From 51a5769cff125ce0911e43c2692d5c707b804242 Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Thu, 29 Aug 2024 20:46:50 -0400
Subject: [PATCH 1/4] Nodes: InstancedPointsNodeMaterial - Add pointWidthNode

---
 .../three/src/materials/nodes/InstancedPointsNodeMaterial.d.ts  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/types/three/src/materials/nodes/InstancedPointsNodeMaterial.d.ts b/types/three/src/materials/nodes/InstancedPointsNodeMaterial.d.ts
index ce74bb1f8..9784e7f52 100644
--- a/types/three/src/materials/nodes/InstancedPointsNodeMaterial.d.ts
+++ b/types/three/src/materials/nodes/InstancedPointsNodeMaterial.d.ts
@@ -9,6 +9,7 @@ export interface InstancedPointsNodeMaterialParameters extends NodeMaterialParam
     useColor?: boolean | undefined;
     pointWidth?: number | undefined;
     pointColorNode?: Node | null | undefined;
+    pointWidthNode?: Node | null | undefined;
 }
 
 declare class InstancedPointsNodeMaterial extends NodeMaterial {
@@ -16,6 +17,7 @@ declare class InstancedPointsNodeMaterial extends NodeMaterial {
     useColor: boolean | undefined;
     pointWidth: number;
     pointColorNode: Node | null;
+    pointWidthNode: Node | null;
 
     // Properties from LineDashedMaterial
     readonly isPointsMaterial: true;

From e84becf3e2d0c1837970fb4cead690b1c116e8fa Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Thu, 29 Aug 2024 20:47:21 -0400
Subject: [PATCH 2/4] Update three.js

---
 three.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/three.js b/three.js
index 3f2956c35..7506a353d 160000
--- a/three.js
+++ b/three.js
@@ -1 +1 @@
-Subproject commit 3f2956c35e1bd1fc8cbe35f1430e475fd848e6a2
+Subproject commit 7506a353d60767f738ca1abeceb6fa11c77122eb

From 1bb9450f9dca59e1cd17caf92340278fb498f599 Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Thu, 29 Aug 2024 20:47:30 -0400
Subject: [PATCH 3/4] Add examples

---
 examples-testing/examples/css2d_label.ts      | 186 ++++
 examples-testing/examples/css3d_molecules.ts  | 353 ++++++++
 .../examples/css3d_orthographic.ts            | 208 +++++
 .../examples/css3d_periodictable.ts           | 793 ++++++++++++++++++
 examples-testing/examples/css3d_sandbox.ts    | 180 ++++
 examples-testing/examples/css3d_sprites.ts    | 157 ++++
 examples-testing/examples/css3d_youtube.ts    |  79 ++
 examples-testing/examples/games_fps.ts        | 372 ++++++++
 .../examples/misc_animation_groups.ts         | 125 +++
 .../examples/misc_animation_keys.ts           | 129 +++
 .../examples/misc_boxselection.ts             | 137 +++
 .../examples/misc_controls_arcball.ts         | 210 +++++
 .../examples/misc_controls_drag.ts            | 153 ++++
 .../examples/misc_controls_fly.ts             | 214 +++++
 .../examples/misc_controls_map.ts             |  98 +++
 .../examples/misc_controls_orbit.ts           |  89 ++
 .../examples/misc_controls_pointerlock.ts     | 245 ++++++
 .../examples/misc_controls_trackball.ts       | 134 +++
 .../examples/misc_controls_transform.ts       | 181 ++++
 .../examples/misc_exporter_draco.ts           | 117 +++
 .../examples/misc_exporter_exr.ts             | 158 ++++
 .../examples/misc_exporter_gltf.ts            | 507 +++++++++++
 .../examples/misc_exporter_obj.ts             | 192 +++++
 .../examples/misc_exporter_ply.ts             | 156 ++++
 .../examples/misc_exporter_stl.ts             | 129 +++
 .../examples/misc_exporter_usdz.ts            | 129 +++
 examples-testing/examples/misc_lookat.ts      |  95 +++
 examples-testing/examples/misc_uv_tests.ts    |  44 +
 .../examples/physics_ammo_instancing.ts       | 119 +++
 .../examples/physics_jolt_instancing.ts       | 119 +++
 .../examples/physics_rapier_instancing.ts     | 119 +++
 examples-testing/examples/svg_lines.ts        |  87 ++
 examples-testing/examples/svg_sandbox.ts      | 212 +++++
 .../examples/webaudio_orientation.ts          | 141 ++++
 examples-testing/examples/webaudio_sandbox.ts | 222 +++++
 examples-testing/examples/webaudio_timing.ts  | 152 ++++
 .../examples/webaudio_visualizer.ts           |  86 ++
 .../examples/webgl_animation_keyframes.ts     |  80 ++
 .../examples/webgl_animation_multiple.ts      | 197 +++++
 .../webgl_animation_skinning_morph.ts         | 187 +++++
 .../examples/webgl_buffergeometry.ts          | 178 ++++
 ...webgl_buffergeometry_attributes_integer.ts | 113 +++
 .../webgl_buffergeometry_attributes_none.ts   |  56 ++
 ...fergeometry_custom_attributes_particles.ts | 103 +++
 .../webgl_buffergeometry_drawrange.ts         | 239 ++++++
 .../webgl_buffergeometry_glbufferattribute.ts | 139 +++
 .../examples/webgl_buffergeometry_indexed.ts  | 137 +++
 .../webgl_buffergeometry_instancing.ts        | 138 +++
 ...gl_buffergeometry_instancing_billboards.ts |  86 ++
 ...l_buffergeometry_instancing_interleaved.ts | 152 ++++
 .../examples/webgl_buffergeometry_lines.ts    | 118 +++
 .../webgl_buffergeometry_lines_indexed.ts     | 179 ++++
 .../examples/webgl_buffergeometry_points.ts   | 109 +++
 ...webgl_buffergeometry_points_interleaved.ts | 122 +++
 .../webgl_buffergeometry_rawshader.ts         |  97 +++
 .../webgl_buffergeometry_selective_draw.ts    | 150 ++++
 .../examples/webgl_buffergeometry_uint.ts     | 177 ++++
 examples-testing/examples/webgl_camera.ts     | 218 +++++
 .../examples/webgl_camera_array.ts            | 104 +++
 .../webgl_camera_logarithmicdepthbuffer.ts    | 248 ++++++
 .../examples/webgl_clipculldistance.ts        | 110 +++
 examples-testing/examples/webgl_clipping.ts   | 195 +++++
 .../examples/webgl_clipping_advanced.ts       | 355 ++++++++
 .../examples/webgl_clipping_intersection.ts   | 137 +++
 .../examples/webgl_clipping_stencil.ts        | 260 ++++++
 .../examples/webgl_custom_attributes.ts       | 100 +++
 .../examples/webgl_custom_attributes_lines.ts | 121 +++
 .../webgl_custom_attributes_points.ts         | 117 +++
 .../webgl_custom_attributes_points2.ts        | 193 +++++
 .../webgl_custom_attributes_points3.ts        | 200 +++++
 examples-testing/examples/webgl_decals.ts     | 237 ++++++
 .../examples/webgl_effects_anaglyph.ts        | 114 +++
 .../examples/webgl_effects_ascii.ts           |  81 ++
 .../examples/webgl_effects_parallaxbarrier.ts | 110 +++
 .../examples/webgl_effects_peppersghost.ts    |  85 ++
 .../examples/webgl_effects_stereo.ts          |  98 +++
 .../examples/webgl_framebuffer_texture.ts     | 151 ++++
 .../examples/webgl_furnace_test.ts            |  96 +++
 examples-testing/examples/webgl_geometries.ts | 137 +++
 .../examples/webgl_geometries_parametric.ts   | 124 +++
 .../examples/webgl_geometry_colors.ts         | 176 ++++
 .../webgl_geometry_colors_lookuptable.ts      | 148 ++++
 .../examples/webgl_geometry_convex.ts         | 117 +++
 .../examples/webgl_geometry_cube.ts           |  46 +
 .../examples/webgl_geometry_dynamic.ts        |  97 +++
 .../examples/webgl_geometry_extrude_shapes.ts | 149 ++++
 .../webgl_geometry_extrude_splines.ts         | 310 +++++++
 .../examples/webgl_geometry_minecraft.ts      | 183 ++++
 .../examples/webgl_geometry_nurbs.ts          | 298 +++++++
 .../examples/webgl_geometry_sdf.ts            | 164 ++++
 .../examples/webgl_geometry_shapes.ts         | 363 ++++++++
 .../examples/webgl_geometry_teapot.ts         | 180 ++++
 .../examples/webgl_geometry_terrain.ts        | 173 ++++
 .../webgl_geometry_terrain_raycast.ts         | 206 +++++
 .../examples/webgl_geometry_text.ts           | 312 +++++++
 .../examples/webgl_geometry_text_shapes.ts    | 112 +++
 .../examples/webgl_geometry_text_stroke.ts    | 116 +++
 .../examples/webgl_gpgpu_birds.ts             | 313 +++++++
 .../examples/webgl_gpgpu_birds_gltf.ts        | 415 +++++++++
 .../examples/webgl_gpgpu_protoplanet.ts       | 280 +++++++
 .../examples/webgl_gpgpu_water.ts             | 397 +++++++++
 examples-testing/examples/webgl_helpers.ts    | 117 +++
 .../examples/webgl_instancing_dynamic.ts      | 103 +++
 .../examples/webgl_instancing_morph.ts        | 147 ++++
 .../examples/webgl_instancing_performance.ts  | 262 ++++++
 .../examples/webgl_instancing_raycast.ts      | 116 +++
 .../examples/webgl_instancing_scatter.ts      | 257 ++++++
 .../webgl_interactive_buffergeometry.ts       | 244 ++++++
 .../examples/webgl_interactive_cubes.ts       | 114 +++
 .../examples/webgl_interactive_cubes_gpu.ts   | 229 +++++
 .../examples/webgl_interactive_cubes_ortho.ts | 129 +++
 .../examples/webgl_interactive_lines.ts       | 160 ++++
 .../examples/webgl_interactive_points.ts      | 143 ++++
 .../webgl_interactive_raycasting_points.ts    | 220 +++++
 .../webgl_interactive_voxelpainter.ts         | 158 ++++
 examples-testing/examples/webgl_layers.ts     | 125 +++
 examples-testing/examples/webgl_lensflares.ts | 137 +++
 examples-testing/examples/webgl_lightprobe.ts | 133 +++
 .../examples/webgl_lightprobe_cubecamera.ts   |  83 ++
 .../examples/webgl_lights_hemisphere.ts       | 188 +++++
 .../examples/webgl_lights_physical.ts         | 237 ++++++
 .../examples/webgl_lights_pointlights.ts      | 100 +++
 .../examples/webgl_lights_rectarealight.ts    |  79 ++
 .../examples/webgl_lights_spotlight.ts        | 183 ++++
 .../examples/webgl_lights_spotlights.ts       | 133 +++
 .../examples/webgl_lines_colors.ts            | 181 ++++
 .../examples/webgl_lines_dashed.ts            | 186 ++++
 examples-testing/examples/webgl_lines_fat.ts  | 251 ++++++
 .../examples/webgl_lines_fat_raycasting.ts    | 294 +++++++
 .../examples/webgl_lines_fat_wireframe.ts     | 210 +++++
 examples-testing/examples/webgl_loader_3dm.ts |  95 +++
 examples-testing/examples/webgl_loader_3ds.ts |  62 ++
 examples-testing/examples/webgl_loader_3mf.ts | 105 +++
 .../examples/webgl_loader_3mf_materials.ts    | 106 +++
 examples-testing/examples/webgl_loader_amf.ts |  62 ++
 examples-testing/examples/webgl_loader_bvh.ts |  61 ++
 .../examples/webgl_loader_collada.ts          |  83 ++
 .../examples/webgl_loader_collada_skinning.ts |  97 +++
 .../examples/webgl_loader_draco.ts            |  85 ++
 examples-testing/examples/webgl_loader_fbx.ts | 162 ++++
 .../examples/webgl_loader_fbx_nurbs.ts        |  61 ++
 .../examples/webgl_loader_gcode.ts            |  49 ++
 .../examples/webgl_loader_gltf.ts             |  74 ++
 .../examples/webgl_loader_gltf_anisotropy.ts  |  68 ++
 .../examples/webgl_loader_gltf_avif.ts        |  61 ++
 .../examples/webgl_loader_gltf_compressed.ts  |  83 ++
 .../examples/webgl_loader_gltf_dispersion.ts  |  66 ++
 .../examples/webgl_loader_gltf_instancing.ts  |  69 ++
 .../examples/webgl_loader_gltf_iridescence.ts |  66 ++
 .../examples/webgl_loader_gltf_sheen.ts       |  72 ++
 .../webgl_loader_gltf_transmission.ts         |  80 ++
 .../examples/webgl_loader_imagebitmap.ts      | 109 +++
 examples-testing/examples/webgl_loader_kmz.ts |  59 ++
 examples-testing/examples/webgl_loader_lwo.ts |  69 ++
 .../examples/webgl_loader_md2_control.ts      | 289 +++++++
 examples-testing/examples/webgl_loader_mdd.ts |  62 ++
 examples-testing/examples/webgl_loader_obj.ts |  98 +++
 .../examples/webgl_loader_obj_mtl.ts          |  82 ++
 examples-testing/examples/webgl_loader_pcd.ts |  65 ++
 examples-testing/examples/webgl_loader_pdb.ts | 208 +++++
 examples-testing/examples/webgl_loader_ply.ts | 146 ++++
 examples-testing/examples/webgl_loader_svg.ts | 193 +++++
 .../examples/webgl_loader_texture_dds.ts      | 207 +++++
 .../examples/webgl_loader_texture_ktx.ts      | 137 +++
 .../examples/webgl_loader_texture_rgbm.ts     |  75 ++
 .../examples/webgl_loader_texture_tga.ts      |  90 ++
 .../examples/webgl_loader_texture_tiff.ts     |  87 ++
 .../examples/webgl_loader_texture_ultrahdr.ts | 101 +++
 .../examples/webgl_loader_tilt.ts             |  54 ++
 examples-testing/examples/webgl_loader_ttf.ts | 231 +++++
 .../examples/webgl_loader_usdz.ts             |  68 ++
 examples-testing/examples/webgl_loader_vox.ts | 104 +++
 .../examples/webgl_loader_vrml.ts             | 118 +++
 examples-testing/examples/webgl_loader_vtk.ts | 123 +++
 examples-testing/examples/webgl_loader_xyz.ts |  62 ++
 examples-testing/examples/webgl_lod.ts        |  88 ++
 .../examples/webgl_marchingcubes.ts           | 311 +++++++
 .../examples/webgl_materials_alphahash.ts     | 178 ++++
 .../examples/webgl_materials_blending.ts      | 147 ++++
 .../webgl_materials_blending_custom.ts        | 214 +++++
 .../examples/webgl_materials_bumpmap.ts       | 140 ++++
 .../examples/webgl_materials_car.ts           | 167 ++++
 .../examples/webgl_materials_cubemap.ts       | 115 +++
 .../webgl_materials_cubemap_dynamic.ts        | 115 +++
 .../webgl_materials_cubemap_mipmaps.ts        | 119 +++
 .../webgl_materials_cubemap_refraction.ts     | 126 +++
 ...bgl_materials_cubemap_render_to_mipmaps.ts | 183 ++++
 .../webgl_materials_displacementmap.ts        | 224 +++++
 .../examples/webgl_materials_envmaps.ts       | 131 +++
 .../examples/webgl_materials_envmaps_exr.ts   | 153 ++++
 ...webgl_materials_envmaps_groundprojected.ts | 150 ++++
 .../examples/webgl_materials_envmaps_hdr.ts   | 176 ++++
 .../examples/webgl_materials_modified.ts      | 115 +++
 .../webgl_materials_normalmap_object_space.ts |  82 ++
 .../webgl_materials_physical_clearcoat.ts     | 208 +++++
 .../webgl_materials_physical_transmission.ts  | 182 ++++
 ...l_materials_physical_transmission_alpha.ts | 192 +++++
 .../webgl_materials_texture_anisotropy.ts     | 143 ++++
 .../webgl_materials_texture_canvas.ts         |  92 ++
 .../webgl_materials_texture_filters.ts        | 164 ++++
 .../webgl_materials_texture_manualmipmap.ts   | 175 ++++
 .../webgl_materials_texture_partialupdate.ts  | 100 +++
 .../webgl_materials_texture_rotation.ts       | 113 +++
 .../examples/webgl_materials_toon.ts          | 152 ++++
 .../examples/webgl_materials_video.ts         | 208 +++++
 .../examples/webgl_materials_video_webcam.ts  |  79 ++
 .../examples/webgl_materials_wireframe.ts     | 107 +++
 examples-testing/examples/webgl_math_obb.ts   | 189 +++++
 .../webgl_math_orientation_transform.ts       |  95 +++
 examples-testing/examples/webgl_mesh_batch.ts | 305 +++++++
 examples-testing/examples/webgl_mirror.ts     | 168 ++++
 .../examples/webgl_modifier_edgesplit.ts      | 136 +++
 .../examples/webgl_modifier_simplifier.ts     |  77 ++
 .../examples/webgl_modifier_tessellation.ts   | 142 ++++
 .../examples/webgl_morphtargets.ts            | 120 +++
 .../examples/webgl_morphtargets_face.ts       | 105 +++
 .../examples/webgl_morphtargets_horse.ts      | 100 +++
 .../examples/webgl_morphtargets_sphere.ts     | 105 +++
 .../examples/webgl_multiple_elements.ts       | 139 +++
 .../examples/webgl_multiple_rendertargets.ts  | 133 +++
 .../webgl_multiple_scenes_comparison.ts       |  98 +++
 .../examples/webgl_multiple_views.ts          | 237 ++++++
 .../webgl_multisampled_renderbuffers.ts       | 133 +++
 .../examples/webgl_panorama_cube.ts           |  83 ++
 .../webgl_panorama_equirectangular.ts         | 112 +++
 .../examples/webgl_performance.ts             |  77 ++
 examples-testing/examples/webgl_pmrem_test.ts | 141 ++++
 .../examples/webgl_points_billboards.ts       | 120 +++
 .../examples/webgl_points_sprites.ts          | 167 ++++
 .../examples/webgl_points_waves.ts            | 145 ++++
 examples-testing/examples/webgl_portal.ts     | 218 +++++
 .../examples/webgl_postprocessing.ts          |  86 ++
 .../examples/webgl_postprocessing_advanced.ts | 304 +++++++
 .../webgl_postprocessing_afterimage.ts        |  72 ++
 .../webgl_postprocessing_backgrounds.ts       | 214 +++++
 .../examples/webgl_postprocessing_fxaa.ts     | 132 +++
 .../examples/webgl_postprocessing_glitch.ts   |  97 +++
 .../examples/webgl_postprocessing_godrays.ts  | 347 ++++++++
 .../examples/webgl_postprocessing_gtao.ts     | 215 +++++
 .../examples/webgl_postprocessing_masking.ts  | 100 +++
 .../webgl_postprocessing_material_ao.ts       | 277 ++++++
 .../examples/webgl_postprocessing_outline.ts  | 282 +++++++
 .../examples/webgl_postprocessing_pixel.ts    | 228 +++++
 .../webgl_postprocessing_procedural.ts        |  77 ++
 .../webgl_postprocessing_rgb_halftone.ts      | 167 ++++
 .../examples/webgl_postprocessing_sao.ts      | 137 +++
 .../examples/webgl_postprocessing_smaa.ts     | 109 +++
 .../examples/webgl_postprocessing_sobel.ts    | 111 +++
 .../examples/webgl_postprocessing_ssaa.ts     | 206 +++++
 .../examples/webgl_postprocessing_ssao.ts     | 118 +++
 .../examples/webgl_postprocessing_ssr.ts      | 261 ++++++
 .../examples/webgl_postprocessing_taa.ts      | 139 +++
 .../webgl_postprocessing_transition.ts        | 211 +++++
 .../webgl_postprocessing_unreal_bloom.ts      | 136 +++
 ...l_postprocessing_unreal_bloom_selective.ts | 195 +++++
 .../examples/webgl_raycaster_sprite.ts        | 103 +++
 .../examples/webgl_raycaster_texture.ts       | 286 +++++++
 .../examples/webgl_raymarching_reflect.ts     |  95 +++
 .../examples/webgl_read_float_buffer.ts       | 153 ++++
 examples-testing/examples/webgl_refraction.ts | 135 +++
 examples-testing/examples/webgl_rtt.ts        | 171 ++++
 examples-testing/examples/webgl_shader.ts     |  50 ++
 .../examples/webgl_shader_lava.ts             | 101 +++
 .../examples/webgl_shaders_ocean.ts           | 169 ++++
 .../examples/webgl_shaders_sky.ts             | 103 +++
 .../examples/webgl_shadow_contact.ts          | 272 ++++++
 examples-testing/examples/webgl_shadowmap.ts  | 311 +++++++
 .../examples/webgl_shadowmap_csm.ts           | 253 ++++++
 .../examples/webgl_shadowmap_pcss.ts          | 161 ++++
 .../examples/webgl_shadowmap_performance.ts   | 281 +++++++
 .../examples/webgl_shadowmap_pointlight.ts    | 139 +++
 .../examples/webgl_shadowmap_progressive.ts   | 204 +++++
 .../examples/webgl_shadowmap_viewer.ts        | 178 ++++
 .../examples/webgl_shadowmap_vsm.ts           | 200 +++++
 examples-testing/examples/webgl_shadowmesh.ts | 250 ++++++
 examples-testing/examples/webgl_simple_gi.ts  | 172 ++++
 examples-testing/examples/webgl_sprites.ts    | 187 +++++
 .../examples/webgl_test_memory.ts             |  65 ++
 .../examples/webgl_test_memory2.ts            |  81 ++
 .../examples/webgl_test_wide_gamut.ts         | 118 +++
 .../webgl_texture2darray_compressed.ts        |  88 ++
 .../webgl_texture2darray_layerupdate.ts       | 131 +++
 examples-testing/examples/webgl_texture3d.ts  | 128 +++
 .../examples/webgl_texture3d_partialupdate.ts | 326 +++++++
 .../examples/webgl_tonemapping.ts             | 163 ++++
 examples-testing/examples/webgl_ubo.ts        | 137 +++
 examples-testing/examples/webgl_ubo_arrays.ts | 171 ++++
 .../examples/webgl_video_kinect.ts            | 113 +++
 .../webgl_video_panorama_equirectangular.ts   |  95 +++
 .../examples/webgl_volume_cloud.ts            | 279 ++++++
 .../examples/webgl_volume_instancing.ts       | 192 +++++
 .../examples/webgl_volume_perlin.ts           | 208 +++++
 examples-testing/examples/webgl_water.ts      | 162 ++++
 .../examples/webgl_water_flowmap.ts           | 100 +++
 .../examples/webgpu_backdrop_area.ts          | 164 ++++
 .../webgpu_camera_logarithmicdepthbuffer.ts   | 245 ++++++
 examples-testing/examples/webgpu_clearcoat.ts | 205 +++++
 examples-testing/examples/webgpu_clipping.ts  | 207 +++++
 .../examples/webgpu_custom_fog_background.ts  |  93 ++
 .../examples/webgpu_display_stereo.ts         | 139 +++
 .../examples/webgpu_instancing_morph.ts       | 148 ++++
 .../examples/webgpu_lightprobe.ts             | 126 +++
 .../examples/webgpu_lights_ies_spotlight.ts   | 117 +++
 .../examples/webgpu_lights_rectarealight.ts   |  79 ++
 .../examples/webgpu_loader_gltf.ts            |  71 ++
 .../examples/webgpu_loader_gltf_anisotropy.ts |  65 ++
 .../examples/webgpu_loader_gltf_compressed.ts |  67 ++
 .../examples/webgpu_loader_gltf_dispersion.ts |  63 ++
 .../webgpu_loader_gltf_iridescence.ts         |  70 ++
 .../examples/webgpu_loader_gltf_sheen.ts      |  81 ++
 .../webgpu_loader_gltf_transmission.ts        |  80 ++
 .../examples/webgpu_materials_basic.ts        | 137 +++
 .../webgpu_materials_displacementmap.ts       | 224 +++++
 .../examples/webgpu_materials_envmaps.ts      | 107 +++
 .../examples/webgpu_materials_lightmap.ts     |  94 +++
 .../examples/webgpu_materials_toon.ts         | 148 ++++
 .../examples/webgpu_materials_transmission.ts | 168 ++++
 .../examples/webgpu_materials_video.ts        | 184 ++++
 .../examples/webgpu_mesh_batch.ts             | 269 ++++++
 .../examples/webgpu_morphtargets.ts           | 121 +++
 .../examples/webgpu_morphtargets_face.ts      | 102 +++
 examples-testing/examples/webgpu_mrt.ts       | 120 +++
 .../webgpu_multiple_rendertargets_readback.ts | 156 ++++
 examples-testing/examples/webgpu_ocean.ts     | 161 ++++
 .../examples/webgpu_parallax_uv.ts            | 112 +++
 .../examples/webgpu_performance.ts            |  84 ++
 .../examples/webgpu_postprocessing.ts         |  77 ++
 .../examples/webgpu_postprocessing_3dlut.ts   | 139 +++
 .../webgpu_postprocessing_afterimage.ts       |  70 ++
 .../examples/webgpu_postprocessing_ao.ts      | 204 +++++
 .../examples/webgpu_postprocessing_bloom.ts   | 127 +++
 .../webgpu_postprocessing_bloom_emissive.ts   | 100 +++
 .../webgpu_postprocessing_bloom_selective.ts  | 122 +++
 .../webgpu_postprocessing_difference.ts       |  92 ++
 .../examples/webgpu_postprocessing_dof.ts     | 160 ++++
 .../examples/webgpu_postprocessing_fxaa.ts    | 122 +++
 .../examples/webgpu_postprocessing_masking.ts |  85 ++
 .../webgpu_postprocessing_motion_blur.ts      | 205 +++++
 .../examples/webgpu_postprocessing_pixel.ts   | 234 ++++++
 .../examples/webgpu_postprocessing_sobel.ts   |  89 ++
 .../examples/webgpu_postprocessing_ssaa.ts    | 181 ++++
 .../webgpu_postprocessing_transition.ts       | 200 +++++
 .../examples/webgpu_procedural_texture.ts     |  74 ++
 .../examples/webgpu_refraction.ts             | 141 ++++
 examples-testing/examples/webgpu_sky.ts       |  95 +++
 .../webgpu_texture2darray_compressed.ts       |  84 ++
 .../examples/webgpu_textures_anisotropy.ts    | 155 ++++
 .../examples/webgpu_textures_partialupdate.ts | 103 +++
 .../examples/webgpu_tsl_coffee_smoke.ts       | 145 ++++
 .../examples/webgpu_tsl_vfx_flames.ts         | 203 +++++
 .../examples/webgpu_video_panorama.ts         |  99 +++
 examples-testing/examples/webgpu_water.ts     | 171 ++++
 examples-testing/examples/webxr_ar_cones.ts   |  66 ++
 examples-testing/examples/webxr_ar_hittest.ts | 115 +++
 .../examples/webxr_ar_lighting.ts             | 124 +++
 .../examples/webxr_ar_plane_detection.ts      |  46 +
 .../examples/webxr_vr_handinput.ts            | 126 +++
 .../examples/webxr_vr_panorama.ts             |  92 ++
 .../examples/webxr_vr_panorama_depth.ts       |  90 ++
 .../examples/webxr_vr_rollercoaster.ts        | 211 +++++
 examples-testing/examples/webxr_vr_sandbox.ts | 192 +++++
 examples-testing/examples/webxr_vr_video.ts   |  92 ++
 .../examples/webxr_xr_controls_transform.ts   | 210 +++++
 .../webxr_xr_dragging_custom_depth.ts         | 395 +++++++++
 364 files changed, 55542 insertions(+)
 create mode 100644 examples-testing/examples/css2d_label.ts
 create mode 100644 examples-testing/examples/css3d_molecules.ts
 create mode 100644 examples-testing/examples/css3d_orthographic.ts
 create mode 100644 examples-testing/examples/css3d_periodictable.ts
 create mode 100644 examples-testing/examples/css3d_sandbox.ts
 create mode 100644 examples-testing/examples/css3d_sprites.ts
 create mode 100644 examples-testing/examples/css3d_youtube.ts
 create mode 100644 examples-testing/examples/games_fps.ts
 create mode 100644 examples-testing/examples/misc_animation_groups.ts
 create mode 100644 examples-testing/examples/misc_animation_keys.ts
 create mode 100644 examples-testing/examples/misc_boxselection.ts
 create mode 100644 examples-testing/examples/misc_controls_arcball.ts
 create mode 100644 examples-testing/examples/misc_controls_drag.ts
 create mode 100644 examples-testing/examples/misc_controls_fly.ts
 create mode 100644 examples-testing/examples/misc_controls_map.ts
 create mode 100644 examples-testing/examples/misc_controls_orbit.ts
 create mode 100644 examples-testing/examples/misc_controls_pointerlock.ts
 create mode 100644 examples-testing/examples/misc_controls_trackball.ts
 create mode 100644 examples-testing/examples/misc_controls_transform.ts
 create mode 100644 examples-testing/examples/misc_exporter_draco.ts
 create mode 100644 examples-testing/examples/misc_exporter_exr.ts
 create mode 100644 examples-testing/examples/misc_exporter_gltf.ts
 create mode 100644 examples-testing/examples/misc_exporter_obj.ts
 create mode 100644 examples-testing/examples/misc_exporter_ply.ts
 create mode 100644 examples-testing/examples/misc_exporter_stl.ts
 create mode 100644 examples-testing/examples/misc_exporter_usdz.ts
 create mode 100644 examples-testing/examples/misc_lookat.ts
 create mode 100644 examples-testing/examples/misc_uv_tests.ts
 create mode 100644 examples-testing/examples/physics_ammo_instancing.ts
 create mode 100644 examples-testing/examples/physics_jolt_instancing.ts
 create mode 100644 examples-testing/examples/physics_rapier_instancing.ts
 create mode 100644 examples-testing/examples/svg_lines.ts
 create mode 100644 examples-testing/examples/svg_sandbox.ts
 create mode 100644 examples-testing/examples/webaudio_orientation.ts
 create mode 100644 examples-testing/examples/webaudio_sandbox.ts
 create mode 100644 examples-testing/examples/webaudio_timing.ts
 create mode 100644 examples-testing/examples/webaudio_visualizer.ts
 create mode 100644 examples-testing/examples/webgl_animation_keyframes.ts
 create mode 100644 examples-testing/examples/webgl_animation_multiple.ts
 create mode 100644 examples-testing/examples/webgl_animation_skinning_morph.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_attributes_none.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_drawrange.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_indexed.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_instancing.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_lines.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_points.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_rawshader.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_selective_draw.ts
 create mode 100644 examples-testing/examples/webgl_buffergeometry_uint.ts
 create mode 100644 examples-testing/examples/webgl_camera.ts
 create mode 100644 examples-testing/examples/webgl_camera_array.ts
 create mode 100644 examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
 create mode 100644 examples-testing/examples/webgl_clipculldistance.ts
 create mode 100644 examples-testing/examples/webgl_clipping.ts
 create mode 100644 examples-testing/examples/webgl_clipping_advanced.ts
 create mode 100644 examples-testing/examples/webgl_clipping_intersection.ts
 create mode 100644 examples-testing/examples/webgl_clipping_stencil.ts
 create mode 100644 examples-testing/examples/webgl_custom_attributes.ts
 create mode 100644 examples-testing/examples/webgl_custom_attributes_lines.ts
 create mode 100644 examples-testing/examples/webgl_custom_attributes_points.ts
 create mode 100644 examples-testing/examples/webgl_custom_attributes_points2.ts
 create mode 100644 examples-testing/examples/webgl_custom_attributes_points3.ts
 create mode 100644 examples-testing/examples/webgl_decals.ts
 create mode 100644 examples-testing/examples/webgl_effects_anaglyph.ts
 create mode 100644 examples-testing/examples/webgl_effects_ascii.ts
 create mode 100644 examples-testing/examples/webgl_effects_parallaxbarrier.ts
 create mode 100644 examples-testing/examples/webgl_effects_peppersghost.ts
 create mode 100644 examples-testing/examples/webgl_effects_stereo.ts
 create mode 100644 examples-testing/examples/webgl_framebuffer_texture.ts
 create mode 100644 examples-testing/examples/webgl_furnace_test.ts
 create mode 100644 examples-testing/examples/webgl_geometries.ts
 create mode 100644 examples-testing/examples/webgl_geometries_parametric.ts
 create mode 100644 examples-testing/examples/webgl_geometry_colors.ts
 create mode 100644 examples-testing/examples/webgl_geometry_colors_lookuptable.ts
 create mode 100644 examples-testing/examples/webgl_geometry_convex.ts
 create mode 100644 examples-testing/examples/webgl_geometry_cube.ts
 create mode 100644 examples-testing/examples/webgl_geometry_dynamic.ts
 create mode 100644 examples-testing/examples/webgl_geometry_extrude_shapes.ts
 create mode 100644 examples-testing/examples/webgl_geometry_extrude_splines.ts
 create mode 100644 examples-testing/examples/webgl_geometry_minecraft.ts
 create mode 100644 examples-testing/examples/webgl_geometry_nurbs.ts
 create mode 100644 examples-testing/examples/webgl_geometry_sdf.ts
 create mode 100644 examples-testing/examples/webgl_geometry_shapes.ts
 create mode 100644 examples-testing/examples/webgl_geometry_teapot.ts
 create mode 100644 examples-testing/examples/webgl_geometry_terrain.ts
 create mode 100644 examples-testing/examples/webgl_geometry_terrain_raycast.ts
 create mode 100644 examples-testing/examples/webgl_geometry_text.ts
 create mode 100644 examples-testing/examples/webgl_geometry_text_shapes.ts
 create mode 100644 examples-testing/examples/webgl_geometry_text_stroke.ts
 create mode 100644 examples-testing/examples/webgl_gpgpu_birds.ts
 create mode 100644 examples-testing/examples/webgl_gpgpu_birds_gltf.ts
 create mode 100644 examples-testing/examples/webgl_gpgpu_protoplanet.ts
 create mode 100644 examples-testing/examples/webgl_gpgpu_water.ts
 create mode 100644 examples-testing/examples/webgl_helpers.ts
 create mode 100644 examples-testing/examples/webgl_instancing_dynamic.ts
 create mode 100644 examples-testing/examples/webgl_instancing_morph.ts
 create mode 100644 examples-testing/examples/webgl_instancing_performance.ts
 create mode 100644 examples-testing/examples/webgl_instancing_raycast.ts
 create mode 100644 examples-testing/examples/webgl_instancing_scatter.ts
 create mode 100644 examples-testing/examples/webgl_interactive_buffergeometry.ts
 create mode 100644 examples-testing/examples/webgl_interactive_cubes.ts
 create mode 100644 examples-testing/examples/webgl_interactive_cubes_gpu.ts
 create mode 100644 examples-testing/examples/webgl_interactive_cubes_ortho.ts
 create mode 100644 examples-testing/examples/webgl_interactive_lines.ts
 create mode 100644 examples-testing/examples/webgl_interactive_points.ts
 create mode 100644 examples-testing/examples/webgl_interactive_raycasting_points.ts
 create mode 100644 examples-testing/examples/webgl_interactive_voxelpainter.ts
 create mode 100644 examples-testing/examples/webgl_layers.ts
 create mode 100644 examples-testing/examples/webgl_lensflares.ts
 create mode 100644 examples-testing/examples/webgl_lightprobe.ts
 create mode 100644 examples-testing/examples/webgl_lightprobe_cubecamera.ts
 create mode 100644 examples-testing/examples/webgl_lights_hemisphere.ts
 create mode 100644 examples-testing/examples/webgl_lights_physical.ts
 create mode 100644 examples-testing/examples/webgl_lights_pointlights.ts
 create mode 100644 examples-testing/examples/webgl_lights_rectarealight.ts
 create mode 100644 examples-testing/examples/webgl_lights_spotlight.ts
 create mode 100644 examples-testing/examples/webgl_lights_spotlights.ts
 create mode 100644 examples-testing/examples/webgl_lines_colors.ts
 create mode 100644 examples-testing/examples/webgl_lines_dashed.ts
 create mode 100644 examples-testing/examples/webgl_lines_fat.ts
 create mode 100644 examples-testing/examples/webgl_lines_fat_raycasting.ts
 create mode 100644 examples-testing/examples/webgl_lines_fat_wireframe.ts
 create mode 100644 examples-testing/examples/webgl_loader_3dm.ts
 create mode 100644 examples-testing/examples/webgl_loader_3ds.ts
 create mode 100644 examples-testing/examples/webgl_loader_3mf.ts
 create mode 100644 examples-testing/examples/webgl_loader_3mf_materials.ts
 create mode 100644 examples-testing/examples/webgl_loader_amf.ts
 create mode 100644 examples-testing/examples/webgl_loader_bvh.ts
 create mode 100644 examples-testing/examples/webgl_loader_collada.ts
 create mode 100644 examples-testing/examples/webgl_loader_collada_skinning.ts
 create mode 100644 examples-testing/examples/webgl_loader_draco.ts
 create mode 100644 examples-testing/examples/webgl_loader_fbx.ts
 create mode 100644 examples-testing/examples/webgl_loader_fbx_nurbs.ts
 create mode 100644 examples-testing/examples/webgl_loader_gcode.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_anisotropy.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_avif.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_compressed.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_dispersion.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_instancing.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_iridescence.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_sheen.ts
 create mode 100644 examples-testing/examples/webgl_loader_gltf_transmission.ts
 create mode 100644 examples-testing/examples/webgl_loader_imagebitmap.ts
 create mode 100644 examples-testing/examples/webgl_loader_kmz.ts
 create mode 100644 examples-testing/examples/webgl_loader_lwo.ts
 create mode 100644 examples-testing/examples/webgl_loader_md2_control.ts
 create mode 100644 examples-testing/examples/webgl_loader_mdd.ts
 create mode 100644 examples-testing/examples/webgl_loader_obj.ts
 create mode 100644 examples-testing/examples/webgl_loader_obj_mtl.ts
 create mode 100644 examples-testing/examples/webgl_loader_pcd.ts
 create mode 100644 examples-testing/examples/webgl_loader_pdb.ts
 create mode 100644 examples-testing/examples/webgl_loader_ply.ts
 create mode 100644 examples-testing/examples/webgl_loader_svg.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_dds.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_ktx.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_rgbm.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_tga.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_tiff.ts
 create mode 100644 examples-testing/examples/webgl_loader_texture_ultrahdr.ts
 create mode 100644 examples-testing/examples/webgl_loader_tilt.ts
 create mode 100644 examples-testing/examples/webgl_loader_ttf.ts
 create mode 100644 examples-testing/examples/webgl_loader_usdz.ts
 create mode 100644 examples-testing/examples/webgl_loader_vox.ts
 create mode 100644 examples-testing/examples/webgl_loader_vrml.ts
 create mode 100644 examples-testing/examples/webgl_loader_vtk.ts
 create mode 100644 examples-testing/examples/webgl_loader_xyz.ts
 create mode 100644 examples-testing/examples/webgl_lod.ts
 create mode 100644 examples-testing/examples/webgl_marchingcubes.ts
 create mode 100644 examples-testing/examples/webgl_materials_alphahash.ts
 create mode 100644 examples-testing/examples/webgl_materials_blending.ts
 create mode 100644 examples-testing/examples/webgl_materials_blending_custom.ts
 create mode 100644 examples-testing/examples/webgl_materials_bumpmap.ts
 create mode 100644 examples-testing/examples/webgl_materials_car.ts
 create mode 100644 examples-testing/examples/webgl_materials_cubemap.ts
 create mode 100644 examples-testing/examples/webgl_materials_cubemap_dynamic.ts
 create mode 100644 examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
 create mode 100644 examples-testing/examples/webgl_materials_cubemap_refraction.ts
 create mode 100644 examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
 create mode 100644 examples-testing/examples/webgl_materials_displacementmap.ts
 create mode 100644 examples-testing/examples/webgl_materials_envmaps.ts
 create mode 100644 examples-testing/examples/webgl_materials_envmaps_exr.ts
 create mode 100644 examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
 create mode 100644 examples-testing/examples/webgl_materials_envmaps_hdr.ts
 create mode 100644 examples-testing/examples/webgl_materials_modified.ts
 create mode 100644 examples-testing/examples/webgl_materials_normalmap_object_space.ts
 create mode 100644 examples-testing/examples/webgl_materials_physical_clearcoat.ts
 create mode 100644 examples-testing/examples/webgl_materials_physical_transmission.ts
 create mode 100644 examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_anisotropy.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_canvas.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_filters.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_manualmipmap.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_partialupdate.ts
 create mode 100644 examples-testing/examples/webgl_materials_texture_rotation.ts
 create mode 100644 examples-testing/examples/webgl_materials_toon.ts
 create mode 100644 examples-testing/examples/webgl_materials_video.ts
 create mode 100644 examples-testing/examples/webgl_materials_video_webcam.ts
 create mode 100644 examples-testing/examples/webgl_materials_wireframe.ts
 create mode 100644 examples-testing/examples/webgl_math_obb.ts
 create mode 100644 examples-testing/examples/webgl_math_orientation_transform.ts
 create mode 100644 examples-testing/examples/webgl_mesh_batch.ts
 create mode 100644 examples-testing/examples/webgl_mirror.ts
 create mode 100644 examples-testing/examples/webgl_modifier_edgesplit.ts
 create mode 100644 examples-testing/examples/webgl_modifier_simplifier.ts
 create mode 100644 examples-testing/examples/webgl_modifier_tessellation.ts
 create mode 100644 examples-testing/examples/webgl_morphtargets.ts
 create mode 100644 examples-testing/examples/webgl_morphtargets_face.ts
 create mode 100644 examples-testing/examples/webgl_morphtargets_horse.ts
 create mode 100644 examples-testing/examples/webgl_morphtargets_sphere.ts
 create mode 100644 examples-testing/examples/webgl_multiple_elements.ts
 create mode 100644 examples-testing/examples/webgl_multiple_rendertargets.ts
 create mode 100644 examples-testing/examples/webgl_multiple_scenes_comparison.ts
 create mode 100644 examples-testing/examples/webgl_multiple_views.ts
 create mode 100644 examples-testing/examples/webgl_multisampled_renderbuffers.ts
 create mode 100644 examples-testing/examples/webgl_panorama_cube.ts
 create mode 100644 examples-testing/examples/webgl_panorama_equirectangular.ts
 create mode 100644 examples-testing/examples/webgl_performance.ts
 create mode 100644 examples-testing/examples/webgl_pmrem_test.ts
 create mode 100644 examples-testing/examples/webgl_points_billboards.ts
 create mode 100644 examples-testing/examples/webgl_points_sprites.ts
 create mode 100644 examples-testing/examples/webgl_points_waves.ts
 create mode 100644 examples-testing/examples/webgl_portal.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_advanced.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_afterimage.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_backgrounds.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_fxaa.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_glitch.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_godrays.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_gtao.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_masking.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_material_ao.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_outline.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_pixel.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_procedural.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_sao.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_smaa.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_sobel.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_ssaa.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_ssao.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_ssr.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_taa.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_transition.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
 create mode 100644 examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
 create mode 100644 examples-testing/examples/webgl_raycaster_sprite.ts
 create mode 100644 examples-testing/examples/webgl_raycaster_texture.ts
 create mode 100644 examples-testing/examples/webgl_raymarching_reflect.ts
 create mode 100644 examples-testing/examples/webgl_read_float_buffer.ts
 create mode 100644 examples-testing/examples/webgl_refraction.ts
 create mode 100644 examples-testing/examples/webgl_rtt.ts
 create mode 100644 examples-testing/examples/webgl_shader.ts
 create mode 100644 examples-testing/examples/webgl_shader_lava.ts
 create mode 100644 examples-testing/examples/webgl_shaders_ocean.ts
 create mode 100644 examples-testing/examples/webgl_shaders_sky.ts
 create mode 100644 examples-testing/examples/webgl_shadow_contact.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_csm.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_pcss.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_performance.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_pointlight.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_progressive.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_viewer.ts
 create mode 100644 examples-testing/examples/webgl_shadowmap_vsm.ts
 create mode 100644 examples-testing/examples/webgl_shadowmesh.ts
 create mode 100644 examples-testing/examples/webgl_simple_gi.ts
 create mode 100644 examples-testing/examples/webgl_sprites.ts
 create mode 100644 examples-testing/examples/webgl_test_memory.ts
 create mode 100644 examples-testing/examples/webgl_test_memory2.ts
 create mode 100644 examples-testing/examples/webgl_test_wide_gamut.ts
 create mode 100644 examples-testing/examples/webgl_texture2darray_compressed.ts
 create mode 100644 examples-testing/examples/webgl_texture2darray_layerupdate.ts
 create mode 100644 examples-testing/examples/webgl_texture3d.ts
 create mode 100644 examples-testing/examples/webgl_texture3d_partialupdate.ts
 create mode 100644 examples-testing/examples/webgl_tonemapping.ts
 create mode 100644 examples-testing/examples/webgl_ubo.ts
 create mode 100644 examples-testing/examples/webgl_ubo_arrays.ts
 create mode 100644 examples-testing/examples/webgl_video_kinect.ts
 create mode 100644 examples-testing/examples/webgl_video_panorama_equirectangular.ts
 create mode 100644 examples-testing/examples/webgl_volume_cloud.ts
 create mode 100644 examples-testing/examples/webgl_volume_instancing.ts
 create mode 100644 examples-testing/examples/webgl_volume_perlin.ts
 create mode 100644 examples-testing/examples/webgl_water.ts
 create mode 100644 examples-testing/examples/webgl_water_flowmap.ts
 create mode 100644 examples-testing/examples/webgpu_backdrop_area.ts
 create mode 100644 examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
 create mode 100644 examples-testing/examples/webgpu_clearcoat.ts
 create mode 100644 examples-testing/examples/webgpu_clipping.ts
 create mode 100644 examples-testing/examples/webgpu_custom_fog_background.ts
 create mode 100644 examples-testing/examples/webgpu_display_stereo.ts
 create mode 100644 examples-testing/examples/webgpu_instancing_morph.ts
 create mode 100644 examples-testing/examples/webgpu_lightprobe.ts
 create mode 100644 examples-testing/examples/webgpu_lights_ies_spotlight.ts
 create mode 100644 examples-testing/examples/webgpu_lights_rectarealight.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_compressed.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_dispersion.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_iridescence.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_sheen.ts
 create mode 100644 examples-testing/examples/webgpu_loader_gltf_transmission.ts
 create mode 100644 examples-testing/examples/webgpu_materials_basic.ts
 create mode 100644 examples-testing/examples/webgpu_materials_displacementmap.ts
 create mode 100644 examples-testing/examples/webgpu_materials_envmaps.ts
 create mode 100644 examples-testing/examples/webgpu_materials_lightmap.ts
 create mode 100644 examples-testing/examples/webgpu_materials_toon.ts
 create mode 100644 examples-testing/examples/webgpu_materials_transmission.ts
 create mode 100644 examples-testing/examples/webgpu_materials_video.ts
 create mode 100644 examples-testing/examples/webgpu_mesh_batch.ts
 create mode 100644 examples-testing/examples/webgpu_morphtargets.ts
 create mode 100644 examples-testing/examples/webgpu_morphtargets_face.ts
 create mode 100644 examples-testing/examples/webgpu_mrt.ts
 create mode 100644 examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
 create mode 100644 examples-testing/examples/webgpu_ocean.ts
 create mode 100644 examples-testing/examples/webgpu_parallax_uv.ts
 create mode 100644 examples-testing/examples/webgpu_performance.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_3dlut.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_afterimage.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_ao.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_bloom.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_difference.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_dof.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_fxaa.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_masking.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_motion_blur.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_pixel.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_sobel.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_ssaa.ts
 create mode 100644 examples-testing/examples/webgpu_postprocessing_transition.ts
 create mode 100644 examples-testing/examples/webgpu_procedural_texture.ts
 create mode 100644 examples-testing/examples/webgpu_refraction.ts
 create mode 100644 examples-testing/examples/webgpu_sky.ts
 create mode 100644 examples-testing/examples/webgpu_texture2darray_compressed.ts
 create mode 100644 examples-testing/examples/webgpu_textures_anisotropy.ts
 create mode 100644 examples-testing/examples/webgpu_textures_partialupdate.ts
 create mode 100644 examples-testing/examples/webgpu_tsl_coffee_smoke.ts
 create mode 100644 examples-testing/examples/webgpu_tsl_vfx_flames.ts
 create mode 100644 examples-testing/examples/webgpu_video_panorama.ts
 create mode 100644 examples-testing/examples/webgpu_water.ts
 create mode 100644 examples-testing/examples/webxr_ar_cones.ts
 create mode 100644 examples-testing/examples/webxr_ar_hittest.ts
 create mode 100644 examples-testing/examples/webxr_ar_lighting.ts
 create mode 100644 examples-testing/examples/webxr_ar_plane_detection.ts
 create mode 100644 examples-testing/examples/webxr_vr_handinput.ts
 create mode 100644 examples-testing/examples/webxr_vr_panorama.ts
 create mode 100644 examples-testing/examples/webxr_vr_panorama_depth.ts
 create mode 100644 examples-testing/examples/webxr_vr_rollercoaster.ts
 create mode 100644 examples-testing/examples/webxr_vr_sandbox.ts
 create mode 100644 examples-testing/examples/webxr_vr_video.ts
 create mode 100644 examples-testing/examples/webxr_xr_controls_transform.ts
 create mode 100644 examples-testing/examples/webxr_xr_dragging_custom_depth.ts

diff --git a/examples-testing/examples/css2d_label.ts b/examples-testing/examples/css2d_label.ts
new file mode 100644
index 000000000..48a2d1f05
--- /dev/null
+++ b/examples-testing/examples/css2d_label.ts
@@ -0,0 +1,186 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let gui;
+
+let camera, scene, renderer, labelRenderer;
+
+const layers = {
+    'Toggle Name': function () {
+        camera.layers.toggle(0);
+    },
+    'Toggle Mass': function () {
+        camera.layers.toggle(1);
+    },
+    'Enable All': function () {
+        camera.layers.enableAll();
+    },
+
+    'Disable All': function () {
+        camera.layers.disableAll();
+    },
+};
+
+const clock = new THREE.Clock();
+const textureLoader = new THREE.TextureLoader();
+
+let moon;
+
+init();
+animate();
+
+function init() {
+    const EARTH_RADIUS = 1;
+    const MOON_RADIUS = 0.27;
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
+    camera.position.set(10, 5, 20);
+    camera.layers.enableAll();
+
+    scene = new THREE.Scene();
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(0, 0, 1);
+    dirLight.layers.enableAll();
+    scene.add(dirLight);
+
+    const axesHelper = new THREE.AxesHelper(5);
+    axesHelper.layers.enableAll();
+    scene.add(axesHelper);
+
+    //
+
+    const earthGeometry = new THREE.SphereGeometry(EARTH_RADIUS, 16, 16);
+    const earthMaterial = new THREE.MeshPhongMaterial({
+        specular: 0x333333,
+        shininess: 5,
+        map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),
+        specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),
+        normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),
+        normalScale: new THREE.Vector2(0.85, 0.85),
+    });
+    earthMaterial.map.colorSpace = THREE.SRGBColorSpace;
+    const earth = new THREE.Mesh(earthGeometry, earthMaterial);
+    scene.add(earth);
+
+    const moonGeometry = new THREE.SphereGeometry(MOON_RADIUS, 16, 16);
+    const moonMaterial = new THREE.MeshPhongMaterial({
+        shininess: 5,
+        map: textureLoader.load('textures/planets/moon_1024.jpg'),
+    });
+    moonMaterial.map.colorSpace = THREE.SRGBColorSpace;
+    moon = new THREE.Mesh(moonGeometry, moonMaterial);
+    scene.add(moon);
+
+    //
+
+    earth.layers.enableAll();
+    moon.layers.enableAll();
+
+    const earthDiv = document.createElement('div');
+    earthDiv.className = 'label';
+    earthDiv.textContent = 'Earth';
+    earthDiv.style.backgroundColor = 'transparent';
+
+    const earthLabel = new CSS2DObject(earthDiv);
+    earthLabel.position.set(1.5 * EARTH_RADIUS, 0, 0);
+    earthLabel.center.set(0, 1);
+    earth.add(earthLabel);
+    earthLabel.layers.set(0);
+
+    const earthMassDiv = document.createElement('div');
+    earthMassDiv.className = 'label';
+    earthMassDiv.textContent = '5.97237e24 kg';
+    earthMassDiv.style.backgroundColor = 'transparent';
+
+    const earthMassLabel = new CSS2DObject(earthMassDiv);
+    earthMassLabel.position.set(1.5 * EARTH_RADIUS, 0, 0);
+    earthMassLabel.center.set(0, 0);
+    earth.add(earthMassLabel);
+    earthMassLabel.layers.set(1);
+
+    const moonDiv = document.createElement('div');
+    moonDiv.className = 'label';
+    moonDiv.textContent = 'Moon';
+    moonDiv.style.backgroundColor = 'transparent';
+
+    const moonLabel = new CSS2DObject(moonDiv);
+    moonLabel.position.set(1.5 * MOON_RADIUS, 0, 0);
+    moonLabel.center.set(0, 1);
+    moon.add(moonLabel);
+    moonLabel.layers.set(0);
+
+    const moonMassDiv = document.createElement('div');
+    moonMassDiv.className = 'label';
+    moonMassDiv.textContent = '7.342e22 kg';
+    moonMassDiv.style.backgroundColor = 'transparent';
+
+    const moonMassLabel = new CSS2DObject(moonMassDiv);
+    moonMassLabel.position.set(1.5 * MOON_RADIUS, 0, 0);
+    moonMassLabel.center.set(0, 0);
+    moon.add(moonMassLabel);
+    moonMassLabel.layers.set(1);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    labelRenderer = new CSS2DRenderer();
+    labelRenderer.setSize(window.innerWidth, window.innerHeight);
+    labelRenderer.domElement.style.position = 'absolute';
+    labelRenderer.domElement.style.top = '0px';
+    document.body.appendChild(labelRenderer.domElement);
+
+    const controls = new OrbitControls(camera, labelRenderer.domElement);
+    controls.minDistance = 5;
+    controls.maxDistance = 100;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    initGui();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    labelRenderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    const elapsed = clock.getElapsedTime();
+
+    moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5);
+
+    renderer.render(scene, camera);
+    labelRenderer.render(scene, camera);
+}
+
+//
+
+function initGui() {
+    gui = new GUI();
+
+    gui.title('Camera Layers');
+
+    gui.add(layers, 'Toggle Name');
+    gui.add(layers, 'Toggle Mass');
+    gui.add(layers, 'Enable All');
+    gui.add(layers, 'Disable All');
+
+    gui.open();
+}
diff --git a/examples-testing/examples/css3d_molecules.ts b/examples-testing/examples/css3d_molecules.ts
new file mode 100644
index 000000000..538472607
--- /dev/null
+++ b/examples-testing/examples/css3d_molecules.ts
@@ -0,0 +1,353 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';
+import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+let controls;
+let root;
+
+const objects = [];
+const tmpVec1 = new THREE.Vector3();
+const tmpVec2 = new THREE.Vector3();
+const tmpVec3 = new THREE.Vector3();
+const tmpVec4 = new THREE.Vector3();
+const offset = new THREE.Vector3();
+
+const VIZ_TYPE = {
+    Atoms: 0,
+    Bonds: 1,
+    'Atoms + Bonds': 2,
+};
+
+const MOLECULES = {
+    Ethanol: 'ethanol.pdb',
+    Aspirin: 'aspirin.pdb',
+    Caffeine: 'caffeine.pdb',
+    Nicotine: 'nicotine.pdb',
+    LSD: 'lsd.pdb',
+    Cocaine: 'cocaine.pdb',
+    Cholesterol: 'cholesterol.pdb',
+    Lycopene: 'lycopene.pdb',
+    Glucose: 'glucose.pdb',
+    'Aluminium oxide': 'Al2O3.pdb',
+    Cubane: 'cubane.pdb',
+    Copper: 'cu.pdb',
+    Fluorite: 'caf2.pdb',
+    Salt: 'nacl.pdb',
+    'YBCO superconductor': 'ybco.pdb',
+    Buckyball: 'buckyball.pdb',
+    // 'Diamond': 'diamond.pdb',
+    Graphite: 'graphite.pdb',
+};
+
+const params = {
+    vizType: 2,
+    molecule: 'caffeine.pdb',
+};
+
+const loader = new PDBLoader();
+const colorSpriteMap = {};
+const baseSprite = document.createElement('img');
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+
+    root = new THREE.Object3D();
+    scene.add(root);
+
+    //
+
+    renderer = new CSS3DRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.getElementById('container').appendChild(renderer.domElement);
+
+    //
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.rotateSpeed = 0.5;
+
+    //
+
+    baseSprite.onload = function () {
+        loadMolecule(params.molecule);
+    };
+
+    baseSprite.src = 'textures/sprites/ball.png';
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'vizType', VIZ_TYPE).onChange(changeVizType);
+    gui.add(params, 'molecule', MOLECULES).onChange(loadMolecule);
+    gui.open();
+}
+
+function changeVizType(value) {
+    if (value === 0) showAtoms();
+    else if (value === 1) showBonds();
+    else showAtomsBonds();
+}
+
+//
+
+function showAtoms() {
+    for (let i = 0; i < objects.length; i++) {
+        const object = objects[i];
+
+        if (object instanceof CSS3DSprite) {
+            object.element.style.display = '';
+            object.visible = true;
+        } else {
+            object.element.style.display = 'none';
+            object.visible = false;
+        }
+    }
+}
+
+function showBonds() {
+    for (let i = 0; i < objects.length; i++) {
+        const object = objects[i];
+
+        if (object instanceof CSS3DSprite) {
+            object.element.style.display = 'none';
+            object.visible = false;
+        } else {
+            object.element.style.display = '';
+            object.element.style.height = object.userData.bondLengthFull;
+            object.visible = true;
+        }
+    }
+}
+
+function showAtomsBonds() {
+    for (let i = 0; i < objects.length; i++) {
+        const object = objects[i];
+
+        object.element.style.display = '';
+        object.visible = true;
+
+        if (!(object instanceof CSS3DSprite)) {
+            object.element.style.height = object.userData.bondLengthShort;
+        }
+    }
+}
+
+//
+
+function colorify(ctx, width, height, color) {
+    const r = color.r,
+        g = color.g,
+        b = color.b;
+
+    const imageData = ctx.getImageData(0, 0, width, height);
+    const data = imageData.data;
+
+    for (let i = 0, l = data.length; i < l; i += 4) {
+        data[i + 0] *= r;
+        data[i + 1] *= g;
+        data[i + 2] *= b;
+    }
+
+    ctx.putImageData(imageData, 0, 0);
+}
+
+function imageToCanvas(image) {
+    const width = image.width;
+    const height = image.height;
+
+    const canvas = document.createElement('canvas');
+
+    canvas.width = width;
+    canvas.height = height;
+
+    const context = canvas.getContext('2d');
+    context.drawImage(image, 0, 0, width, height);
+
+    return canvas;
+}
+
+//
+
+function loadMolecule(model) {
+    const url = 'models/pdb/' + model;
+
+    for (let i = 0; i < objects.length; i++) {
+        const object = objects[i];
+        object.parent.remove(object);
+    }
+
+    objects.length = 0;
+
+    loader.load(url, function (pdb) {
+        const geometryAtoms = pdb.geometryAtoms;
+        const geometryBonds = pdb.geometryBonds;
+        const json = pdb.json;
+
+        geometryAtoms.computeBoundingBox();
+        geometryAtoms.boundingBox.getCenter(offset).negate();
+
+        geometryAtoms.translate(offset.x, offset.y, offset.z);
+        geometryBonds.translate(offset.x, offset.y, offset.z);
+
+        const positionAtoms = geometryAtoms.getAttribute('position');
+        const colorAtoms = geometryAtoms.getAttribute('color');
+
+        const position = new THREE.Vector3();
+        const color = new THREE.Color();
+
+        for (let i = 0; i < positionAtoms.count; i++) {
+            position.fromBufferAttribute(positionAtoms, i);
+            color.fromBufferAttribute(colorAtoms, i);
+
+            const atomJSON = json.atoms[i];
+            const element = atomJSON[4];
+
+            if (!colorSpriteMap[element]) {
+                const canvas = imageToCanvas(baseSprite);
+                const context = canvas.getContext('2d');
+
+                colorify(context, canvas.width, canvas.height, color);
+
+                const dataUrl = canvas.toDataURL();
+
+                colorSpriteMap[element] = dataUrl;
+            }
+
+            const colorSprite = colorSpriteMap[element];
+
+            const atom = document.createElement('img');
+            atom.src = colorSprite;
+
+            const object = new CSS3DSprite(atom);
+            object.position.copy(position);
+            object.position.multiplyScalar(75);
+
+            object.matrixAutoUpdate = false;
+            object.updateMatrix();
+
+            root.add(object);
+
+            objects.push(object);
+        }
+
+        const positionBonds = geometryBonds.getAttribute('position');
+
+        const start = new THREE.Vector3();
+        const end = new THREE.Vector3();
+
+        for (let i = 0; i < positionBonds.count; i += 2) {
+            start.fromBufferAttribute(positionBonds, i);
+            end.fromBufferAttribute(positionBonds, i + 1);
+
+            start.multiplyScalar(75);
+            end.multiplyScalar(75);
+
+            tmpVec1.subVectors(end, start);
+            const bondLength = tmpVec1.length() - 50;
+
+            //
+
+            let bond = document.createElement('div');
+            bond.className = 'bond';
+            bond.style.height = bondLength + 'px';
+
+            let object = new CSS3DObject(bond);
+            object.position.copy(start);
+            object.position.lerp(end, 0.5);
+
+            object.userData.bondLengthShort = bondLength + 'px';
+            object.userData.bondLengthFull = bondLength + 55 + 'px';
+
+            //
+
+            const axis = tmpVec2.set(0, 1, 0).cross(tmpVec1);
+            const radians = Math.acos(tmpVec3.set(0, 1, 0).dot(tmpVec4.copy(tmpVec1).normalize()));
+
+            const objMatrix = new THREE.Matrix4().makeRotationAxis(axis.normalize(), radians);
+            object.matrix.copy(objMatrix);
+            object.quaternion.setFromRotationMatrix(object.matrix);
+
+            object.matrixAutoUpdate = false;
+            object.updateMatrix();
+
+            root.add(object);
+
+            objects.push(object);
+
+            //
+
+            const joint = new THREE.Object3D();
+            joint.position.copy(start);
+            joint.position.lerp(end, 0.5);
+
+            joint.matrix.copy(objMatrix);
+            joint.quaternion.setFromRotationMatrix(joint.matrix);
+
+            joint.matrixAutoUpdate = false;
+            joint.updateMatrix();
+
+            bond = document.createElement('div');
+            bond.className = 'bond';
+            bond.style.height = bondLength + 'px';
+
+            object = new CSS3DObject(bond);
+            object.rotation.y = Math.PI / 2;
+
+            object.matrixAutoUpdate = false;
+            object.updateMatrix();
+
+            object.userData.bondLengthShort = bondLength + 'px';
+            object.userData.bondLengthFull = bondLength + 55 + 'px';
+
+            object.userData.joint = joint;
+
+            joint.add(object);
+            root.add(joint);
+
+            objects.push(object);
+        }
+
+        //console.log( "CSS3DObjects:", objects.length );
+
+        changeVizType(params.vizType);
+    });
+}
+
+//
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+    controls.update();
+
+    const time = Date.now() * 0.0004;
+
+    root.rotation.x = time;
+    root.rotation.y = time * 0.7;
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/css3d_orthographic.ts b/examples-testing/examples/css3d_orthographic.ts
new file mode 100644
index 000000000..4aabbed08
--- /dev/null
+++ b/examples-testing/examples/css3d_orthographic.ts
@@ -0,0 +1,208 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+let scene2, renderer2;
+
+const frustumSize = 500;
+
+init();
+animate();
+
+function init() {
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera(
+        (frustumSize * aspect) / -2,
+        (frustumSize * aspect) / 2,
+        frustumSize / 2,
+        frustumSize / -2,
+        1,
+        1000,
+    );
+
+    camera.position.set(-200, 200, 200);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    scene2 = new THREE.Scene();
+
+    const material = new THREE.MeshBasicMaterial({
+        color: 0x000000,
+        wireframe: true,
+        wireframeLinewidth: 1,
+        side: THREE.DoubleSide,
+    });
+
+    // left
+    createPlane(
+        100,
+        100,
+        'chocolate',
+        new THREE.Vector3(-50, 0, 0),
+        new THREE.Euler(0, -90 * THREE.MathUtils.DEG2RAD, 0),
+    );
+    // right
+    createPlane(100, 100, 'saddlebrown', new THREE.Vector3(0, 0, 50), new THREE.Euler(0, 0, 0));
+    // top
+    createPlane(
+        100,
+        100,
+        'yellowgreen',
+        new THREE.Vector3(0, 50, 0),
+        new THREE.Euler(-90 * THREE.MathUtils.DEG2RAD, 0, 0),
+    );
+    // bottom
+    createPlane(
+        300,
+        300,
+        'seagreen',
+        new THREE.Vector3(0, -50, 0),
+        new THREE.Euler(-90 * THREE.MathUtils.DEG2RAD, 0, 0),
+    );
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    renderer2 = new CSS3DRenderer();
+    renderer2.setSize(window.innerWidth, window.innerHeight);
+    renderer2.domElement.style.position = 'absolute';
+    renderer2.domElement.style.top = 0;
+    document.body.appendChild(renderer2.domElement);
+
+    const controls = new OrbitControls(camera, renderer2.domElement);
+    controls.minZoom = 0.5;
+    controls.maxZoom = 2;
+
+    function createPlane(width, height, cssColor, pos, rot) {
+        const element = document.createElement('div');
+        element.style.width = width + 'px';
+        element.style.height = height + 'px';
+        element.style.opacity = 0.75;
+        element.style.background = cssColor;
+
+        const object = new CSS3DObject(element);
+        object.position.copy(pos);
+        object.rotation.copy(rot);
+        scene2.add(object);
+
+        const geometry = new THREE.PlaneGeometry(width, height);
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.copy(object.position);
+        mesh.rotation.copy(object.rotation);
+        scene.add(mesh);
+    }
+
+    window.addEventListener('resize', onWindowResize);
+    createPanel();
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera.left = (-frustumSize * aspect) / 2;
+    camera.right = (frustumSize * aspect) / 2;
+    camera.top = frustumSize / 2;
+    camera.bottom = -frustumSize / 2;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    renderer2.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    renderer.render(scene, camera);
+    renderer2.render(scene2, camera);
+}
+
+function createPanel() {
+    const panel = new GUI();
+    const folder1 = panel.addFolder('camera setViewOffset').close();
+
+    const settings = {
+        setViewOffset() {
+            folder1.children[1].enable().setValue(window.innerWidth);
+            folder1.children[2].enable().setValue(window.innerHeight);
+            folder1.children[3].enable().setValue(0);
+            folder1.children[4].enable().setValue(0);
+            folder1.children[5].enable().setValue(window.innerWidth);
+            folder1.children[6].enable().setValue(window.innerHeight);
+        },
+        fullWidth: 0,
+        fullHeight: 0,
+        offsetX: 0,
+        offsetY: 0,
+        width: 0,
+        height: 0,
+        clearViewOffset() {
+            folder1.children[1].setValue(0).disable();
+            folder1.children[2].setValue(0).disable();
+            folder1.children[3].setValue(0).disable();
+            folder1.children[4].setValue(0).disable();
+            folder1.children[5].setValue(0).disable();
+            folder1.children[6].setValue(0).disable();
+            camera.clearViewOffset();
+        },
+    };
+
+    folder1.add(settings, 'setViewOffset');
+    folder1
+        .add(settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1)
+        .onChange(val => updateCameraViewOffset({ fullWidth: val }))
+        .disable();
+    folder1
+        .add(settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1)
+        .onChange(val => updateCameraViewOffset({ fullHeight: val }))
+        .disable();
+    folder1
+        .add(settings, 'offsetX', 0, 256, 1)
+        .onChange(val => updateCameraViewOffset({ x: val }))
+        .disable();
+    folder1
+        .add(settings, 'offsetY', 0, 256, 1)
+        .onChange(val => updateCameraViewOffset({ y: val }))
+        .disable();
+    folder1
+        .add(settings, 'width', window.screen.width / 4, window.screen.width * 2, 1)
+        .onChange(val => updateCameraViewOffset({ width: val }))
+        .disable();
+    folder1
+        .add(settings, 'height', window.screen.height / 4, window.screen.height * 2, 1)
+        .onChange(val => updateCameraViewOffset({ height: val }))
+        .disable();
+    folder1.add(settings, 'clearViewOffset');
+}
+
+function updateCameraViewOffset({ fullWidth, fullHeight, x, y, width, height }) {
+    if (!camera.view) {
+        camera.setViewOffset(
+            fullWidth || window.innerWidth,
+            fullHeight || window.innerHeight,
+            x || 0,
+            y || 0,
+            width || window.innerWidth,
+            height || window.innerHeight,
+        );
+    } else {
+        camera.setViewOffset(
+            fullWidth || camera.view.fullWidth,
+            fullHeight || camera.view.fullHeight,
+            x || camera.view.offsetX,
+            y || camera.view.offsetY,
+            width || camera.view.width,
+            height || camera.view.height,
+        );
+    }
+}
diff --git a/examples-testing/examples/css3d_periodictable.ts b/examples-testing/examples/css3d_periodictable.ts
new file mode 100644
index 000000000..e3a33f796
--- /dev/null
+++ b/examples-testing/examples/css3d_periodictable.ts
@@ -0,0 +1,793 @@
+import * as THREE from 'three';
+
+import TWEEN from 'three/addons/libs/tween.module.js';
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
+
+const table = [
+    'H',
+    'Hydrogen',
+    '1.00794',
+    1,
+    1,
+    'He',
+    'Helium',
+    '4.002602',
+    18,
+    1,
+    'Li',
+    'Lithium',
+    '6.941',
+    1,
+    2,
+    'Be',
+    'Beryllium',
+    '9.012182',
+    2,
+    2,
+    'B',
+    'Boron',
+    '10.811',
+    13,
+    2,
+    'C',
+    'Carbon',
+    '12.0107',
+    14,
+    2,
+    'N',
+    'Nitrogen',
+    '14.0067',
+    15,
+    2,
+    'O',
+    'Oxygen',
+    '15.9994',
+    16,
+    2,
+    'F',
+    'Fluorine',
+    '18.9984032',
+    17,
+    2,
+    'Ne',
+    'Neon',
+    '20.1797',
+    18,
+    2,
+    'Na',
+    'Sodium',
+    '22.98976...',
+    1,
+    3,
+    'Mg',
+    'Magnesium',
+    '24.305',
+    2,
+    3,
+    'Al',
+    'Aluminium',
+    '26.9815386',
+    13,
+    3,
+    'Si',
+    'Silicon',
+    '28.0855',
+    14,
+    3,
+    'P',
+    'Phosphorus',
+    '30.973762',
+    15,
+    3,
+    'S',
+    'Sulfur',
+    '32.065',
+    16,
+    3,
+    'Cl',
+    'Chlorine',
+    '35.453',
+    17,
+    3,
+    'Ar',
+    'Argon',
+    '39.948',
+    18,
+    3,
+    'K',
+    'Potassium',
+    '39.948',
+    1,
+    4,
+    'Ca',
+    'Calcium',
+    '40.078',
+    2,
+    4,
+    'Sc',
+    'Scandium',
+    '44.955912',
+    3,
+    4,
+    'Ti',
+    'Titanium',
+    '47.867',
+    4,
+    4,
+    'V',
+    'Vanadium',
+    '50.9415',
+    5,
+    4,
+    'Cr',
+    'Chromium',
+    '51.9961',
+    6,
+    4,
+    'Mn',
+    'Manganese',
+    '54.938045',
+    7,
+    4,
+    'Fe',
+    'Iron',
+    '55.845',
+    8,
+    4,
+    'Co',
+    'Cobalt',
+    '58.933195',
+    9,
+    4,
+    'Ni',
+    'Nickel',
+    '58.6934',
+    10,
+    4,
+    'Cu',
+    'Copper',
+    '63.546',
+    11,
+    4,
+    'Zn',
+    'Zinc',
+    '65.38',
+    12,
+    4,
+    'Ga',
+    'Gallium',
+    '69.723',
+    13,
+    4,
+    'Ge',
+    'Germanium',
+    '72.63',
+    14,
+    4,
+    'As',
+    'Arsenic',
+    '74.9216',
+    15,
+    4,
+    'Se',
+    'Selenium',
+    '78.96',
+    16,
+    4,
+    'Br',
+    'Bromine',
+    '79.904',
+    17,
+    4,
+    'Kr',
+    'Krypton',
+    '83.798',
+    18,
+    4,
+    'Rb',
+    'Rubidium',
+    '85.4678',
+    1,
+    5,
+    'Sr',
+    'Strontium',
+    '87.62',
+    2,
+    5,
+    'Y',
+    'Yttrium',
+    '88.90585',
+    3,
+    5,
+    'Zr',
+    'Zirconium',
+    '91.224',
+    4,
+    5,
+    'Nb',
+    'Niobium',
+    '92.90628',
+    5,
+    5,
+    'Mo',
+    'Molybdenum',
+    '95.96',
+    6,
+    5,
+    'Tc',
+    'Technetium',
+    '(98)',
+    7,
+    5,
+    'Ru',
+    'Ruthenium',
+    '101.07',
+    8,
+    5,
+    'Rh',
+    'Rhodium',
+    '102.9055',
+    9,
+    5,
+    'Pd',
+    'Palladium',
+    '106.42',
+    10,
+    5,
+    'Ag',
+    'Silver',
+    '107.8682',
+    11,
+    5,
+    'Cd',
+    'Cadmium',
+    '112.411',
+    12,
+    5,
+    'In',
+    'Indium',
+    '114.818',
+    13,
+    5,
+    'Sn',
+    'Tin',
+    '118.71',
+    14,
+    5,
+    'Sb',
+    'Antimony',
+    '121.76',
+    15,
+    5,
+    'Te',
+    'Tellurium',
+    '127.6',
+    16,
+    5,
+    'I',
+    'Iodine',
+    '126.90447',
+    17,
+    5,
+    'Xe',
+    'Xenon',
+    '131.293',
+    18,
+    5,
+    'Cs',
+    'Caesium',
+    '132.9054',
+    1,
+    6,
+    'Ba',
+    'Barium',
+    '132.9054',
+    2,
+    6,
+    'La',
+    'Lanthanum',
+    '138.90547',
+    4,
+    9,
+    'Ce',
+    'Cerium',
+    '140.116',
+    5,
+    9,
+    'Pr',
+    'Praseodymium',
+    '140.90765',
+    6,
+    9,
+    'Nd',
+    'Neodymium',
+    '144.242',
+    7,
+    9,
+    'Pm',
+    'Promethium',
+    '(145)',
+    8,
+    9,
+    'Sm',
+    'Samarium',
+    '150.36',
+    9,
+    9,
+    'Eu',
+    'Europium',
+    '151.964',
+    10,
+    9,
+    'Gd',
+    'Gadolinium',
+    '157.25',
+    11,
+    9,
+    'Tb',
+    'Terbium',
+    '158.92535',
+    12,
+    9,
+    'Dy',
+    'Dysprosium',
+    '162.5',
+    13,
+    9,
+    'Ho',
+    'Holmium',
+    '164.93032',
+    14,
+    9,
+    'Er',
+    'Erbium',
+    '167.259',
+    15,
+    9,
+    'Tm',
+    'Thulium',
+    '168.93421',
+    16,
+    9,
+    'Yb',
+    'Ytterbium',
+    '173.054',
+    17,
+    9,
+    'Lu',
+    'Lutetium',
+    '174.9668',
+    18,
+    9,
+    'Hf',
+    'Hafnium',
+    '178.49',
+    4,
+    6,
+    'Ta',
+    'Tantalum',
+    '180.94788',
+    5,
+    6,
+    'W',
+    'Tungsten',
+    '183.84',
+    6,
+    6,
+    'Re',
+    'Rhenium',
+    '186.207',
+    7,
+    6,
+    'Os',
+    'Osmium',
+    '190.23',
+    8,
+    6,
+    'Ir',
+    'Iridium',
+    '192.217',
+    9,
+    6,
+    'Pt',
+    'Platinum',
+    '195.084',
+    10,
+    6,
+    'Au',
+    'Gold',
+    '196.966569',
+    11,
+    6,
+    'Hg',
+    'Mercury',
+    '200.59',
+    12,
+    6,
+    'Tl',
+    'Thallium',
+    '204.3833',
+    13,
+    6,
+    'Pb',
+    'Lead',
+    '207.2',
+    14,
+    6,
+    'Bi',
+    'Bismuth',
+    '208.9804',
+    15,
+    6,
+    'Po',
+    'Polonium',
+    '(209)',
+    16,
+    6,
+    'At',
+    'Astatine',
+    '(210)',
+    17,
+    6,
+    'Rn',
+    'Radon',
+    '(222)',
+    18,
+    6,
+    'Fr',
+    'Francium',
+    '(223)',
+    1,
+    7,
+    'Ra',
+    'Radium',
+    '(226)',
+    2,
+    7,
+    'Ac',
+    'Actinium',
+    '(227)',
+    4,
+    10,
+    'Th',
+    'Thorium',
+    '232.03806',
+    5,
+    10,
+    'Pa',
+    'Protactinium',
+    '231.0588',
+    6,
+    10,
+    'U',
+    'Uranium',
+    '238.02891',
+    7,
+    10,
+    'Np',
+    'Neptunium',
+    '(237)',
+    8,
+    10,
+    'Pu',
+    'Plutonium',
+    '(244)',
+    9,
+    10,
+    'Am',
+    'Americium',
+    '(243)',
+    10,
+    10,
+    'Cm',
+    'Curium',
+    '(247)',
+    11,
+    10,
+    'Bk',
+    'Berkelium',
+    '(247)',
+    12,
+    10,
+    'Cf',
+    'Californium',
+    '(251)',
+    13,
+    10,
+    'Es',
+    'Einstenium',
+    '(252)',
+    14,
+    10,
+    'Fm',
+    'Fermium',
+    '(257)',
+    15,
+    10,
+    'Md',
+    'Mendelevium',
+    '(258)',
+    16,
+    10,
+    'No',
+    'Nobelium',
+    '(259)',
+    17,
+    10,
+    'Lr',
+    'Lawrencium',
+    '(262)',
+    18,
+    10,
+    'Rf',
+    'Rutherfordium',
+    '(267)',
+    4,
+    7,
+    'Db',
+    'Dubnium',
+    '(268)',
+    5,
+    7,
+    'Sg',
+    'Seaborgium',
+    '(271)',
+    6,
+    7,
+    'Bh',
+    'Bohrium',
+    '(272)',
+    7,
+    7,
+    'Hs',
+    'Hassium',
+    '(270)',
+    8,
+    7,
+    'Mt',
+    'Meitnerium',
+    '(276)',
+    9,
+    7,
+    'Ds',
+    'Darmstadium',
+    '(281)',
+    10,
+    7,
+    'Rg',
+    'Roentgenium',
+    '(280)',
+    11,
+    7,
+    'Cn',
+    'Copernicium',
+    '(285)',
+    12,
+    7,
+    'Nh',
+    'Nihonium',
+    '(286)',
+    13,
+    7,
+    'Fl',
+    'Flerovium',
+    '(289)',
+    14,
+    7,
+    'Mc',
+    'Moscovium',
+    '(290)',
+    15,
+    7,
+    'Lv',
+    'Livermorium',
+    '(293)',
+    16,
+    7,
+    'Ts',
+    'Tennessine',
+    '(294)',
+    17,
+    7,
+    'Og',
+    'Oganesson',
+    '(294)',
+    18,
+    7,
+];
+
+let camera, scene, renderer;
+let controls;
+
+const objects = [];
+const targets = { table: [], sphere: [], helix: [], grid: [] };
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 3000;
+
+    scene = new THREE.Scene();
+
+    // table
+
+    for (let i = 0; i < table.length; i += 5) {
+        const element = document.createElement('div');
+        element.className = 'element';
+        element.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')';
+
+        const number = document.createElement('div');
+        number.className = 'number';
+        number.textContent = i / 5 + 1;
+        element.appendChild(number);
+
+        const symbol = document.createElement('div');
+        symbol.className = 'symbol';
+        symbol.textContent = table[i];
+        element.appendChild(symbol);
+
+        const details = document.createElement('div');
+        details.className = 'details';
+        details.innerHTML = table[i + 1] + '<br>' + table[i + 2];
+        element.appendChild(details);
+
+        const objectCSS = new CSS3DObject(element);
+        objectCSS.position.x = Math.random() * 4000 - 2000;
+        objectCSS.position.y = Math.random() * 4000 - 2000;
+        objectCSS.position.z = Math.random() * 4000 - 2000;
+        scene.add(objectCSS);
+
+        objects.push(objectCSS);
+
+        //
+
+        const object = new THREE.Object3D();
+        object.position.x = table[i + 3] * 140 - 1330;
+        object.position.y = -(table[i + 4] * 180) + 990;
+
+        targets.table.push(object);
+    }
+
+    // sphere
+
+    const vector = new THREE.Vector3();
+
+    for (let i = 0, l = objects.length; i < l; i++) {
+        const phi = Math.acos(-1 + (2 * i) / l);
+        const theta = Math.sqrt(l * Math.PI) * phi;
+
+        const object = new THREE.Object3D();
+
+        object.position.setFromSphericalCoords(800, phi, theta);
+
+        vector.copy(object.position).multiplyScalar(2);
+
+        object.lookAt(vector);
+
+        targets.sphere.push(object);
+    }
+
+    // helix
+
+    for (let i = 0, l = objects.length; i < l; i++) {
+        const theta = i * 0.175 + Math.PI;
+        const y = -(i * 8) + 450;
+
+        const object = new THREE.Object3D();
+
+        object.position.setFromCylindricalCoords(900, theta, y);
+
+        vector.x = object.position.x * 2;
+        vector.y = object.position.y;
+        vector.z = object.position.z * 2;
+
+        object.lookAt(vector);
+
+        targets.helix.push(object);
+    }
+
+    // grid
+
+    for (let i = 0; i < objects.length; i++) {
+        const object = new THREE.Object3D();
+
+        object.position.x = (i % 5) * 400 - 800;
+        object.position.y = -(Math.floor(i / 5) % 5) * 400 + 800;
+        object.position.z = Math.floor(i / 25) * 1000 - 2000;
+
+        targets.grid.push(object);
+    }
+
+    //
+
+    renderer = new CSS3DRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.getElementById('container').appendChild(renderer.domElement);
+
+    //
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.minDistance = 500;
+    controls.maxDistance = 6000;
+    controls.addEventListener('change', render);
+
+    const buttonTable = document.getElementById('table');
+    buttonTable.addEventListener('click', function () {
+        transform(targets.table, 2000);
+    });
+
+    const buttonSphere = document.getElementById('sphere');
+    buttonSphere.addEventListener('click', function () {
+        transform(targets.sphere, 2000);
+    });
+
+    const buttonHelix = document.getElementById('helix');
+    buttonHelix.addEventListener('click', function () {
+        transform(targets.helix, 2000);
+    });
+
+    const buttonGrid = document.getElementById('grid');
+    buttonGrid.addEventListener('click', function () {
+        transform(targets.grid, 2000);
+    });
+
+    transform(targets.table, 2000);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function transform(targets, duration) {
+    TWEEN.removeAll();
+
+    for (let i = 0; i < objects.length; i++) {
+        const object = objects[i];
+        const target = targets[i];
+
+        new TWEEN.Tween(object.position)
+            .to(
+                { x: target.position.x, y: target.position.y, z: target.position.z },
+                Math.random() * duration + duration,
+            )
+            .easing(TWEEN.Easing.Exponential.InOut)
+            .start();
+
+        new TWEEN.Tween(object.rotation)
+            .to(
+                { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z },
+                Math.random() * duration + duration,
+            )
+            .easing(TWEEN.Easing.Exponential.InOut)
+            .start();
+    }
+
+    new TWEEN.Tween(this)
+        .to({}, duration * 2)
+        .onUpdate(render)
+        .start();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    TWEEN.update();
+
+    controls.update();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/css3d_sandbox.ts b/examples-testing/examples/css3d_sandbox.ts
new file mode 100644
index 000000000..1088b84b1
--- /dev/null
+++ b/examples-testing/examples/css3d_sandbox.ts
@@ -0,0 +1,180 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+let scene2, renderer2;
+
+let controls;
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(200, 200, 200);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    scene2 = new THREE.Scene();
+
+    const material = new THREE.MeshBasicMaterial({
+        color: 0x000000,
+        wireframe: true,
+        wireframeLinewidth: 1,
+        side: THREE.DoubleSide,
+    });
+
+    //
+
+    for (let i = 0; i < 10; i++) {
+        const element = document.createElement('div');
+        element.style.width = '100px';
+        element.style.height = '100px';
+        element.style.opacity = i < 5 ? 0.5 : 1;
+        element.style.background = new THREE.Color(Math.random() * 0xffffff).getStyle();
+
+        const object = new CSS3DObject(element);
+        object.position.x = Math.random() * 200 - 100;
+        object.position.y = Math.random() * 200 - 100;
+        object.position.z = Math.random() * 200 - 100;
+        object.rotation.x = Math.random();
+        object.rotation.y = Math.random();
+        object.rotation.z = Math.random();
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        scene2.add(object);
+
+        const geometry = new THREE.PlaneGeometry(100, 100);
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.copy(object.position);
+        mesh.rotation.copy(object.rotation);
+        mesh.scale.copy(object.scale);
+        scene.add(mesh);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    renderer2 = new CSS3DRenderer();
+    renderer2.setSize(window.innerWidth, window.innerHeight);
+    renderer2.domElement.style.position = 'absolute';
+    renderer2.domElement.style.top = 0;
+    document.body.appendChild(renderer2.domElement);
+
+    controls = new TrackballControls(camera, renderer2.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+    createPanel();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    renderer2.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    controls.update();
+
+    renderer.render(scene, camera);
+    renderer2.render(scene2, camera);
+}
+
+function createPanel() {
+    const panel = new GUI();
+    const folder1 = panel.addFolder('camera setViewOffset').close();
+
+    const settings = {
+        setViewOffset() {
+            folder1.children[1].enable().setValue(window.innerWidth);
+            folder1.children[2].enable().setValue(window.innerHeight);
+            folder1.children[3].enable().setValue(0);
+            folder1.children[4].enable().setValue(0);
+            folder1.children[5].enable().setValue(window.innerWidth);
+            folder1.children[6].enable().setValue(window.innerHeight);
+        },
+        fullWidth: 0,
+        fullHeight: 0,
+        offsetX: 0,
+        offsetY: 0,
+        width: 0,
+        height: 0,
+        clearViewOffset() {
+            folder1.children[1].setValue(0).disable();
+            folder1.children[2].setValue(0).disable();
+            folder1.children[3].setValue(0).disable();
+            folder1.children[4].setValue(0).disable();
+            folder1.children[5].setValue(0).disable();
+            folder1.children[6].setValue(0).disable();
+            camera.clearViewOffset();
+        },
+    };
+
+    folder1.add(settings, 'setViewOffset');
+    folder1
+        .add(settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1)
+        .onChange(val => updateCameraViewOffset({ fullWidth: val }))
+        .disable();
+    folder1
+        .add(settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1)
+        .onChange(val => updateCameraViewOffset({ fullHeight: val }))
+        .disable();
+    folder1
+        .add(settings, 'offsetX', 0, 256, 1)
+        .onChange(val => updateCameraViewOffset({ x: val }))
+        .disable();
+    folder1
+        .add(settings, 'offsetY', 0, 256, 1)
+        .onChange(val => updateCameraViewOffset({ y: val }))
+        .disable();
+    folder1
+        .add(settings, 'width', window.screen.width / 4, window.screen.width * 2, 1)
+        .onChange(val => updateCameraViewOffset({ width: val }))
+        .disable();
+    folder1
+        .add(settings, 'height', window.screen.height / 4, window.screen.height * 2, 1)
+        .onChange(val => updateCameraViewOffset({ height: val }))
+        .disable();
+    folder1.add(settings, 'clearViewOffset');
+}
+
+function updateCameraViewOffset({ fullWidth, fullHeight, x, y, width, height }) {
+    if (!camera.view) {
+        camera.setViewOffset(
+            fullWidth || window.innerWidth,
+            fullHeight || window.innerHeight,
+            x || 0,
+            y || 0,
+            width || window.innerWidth,
+            height || window.innerHeight,
+        );
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+    } else {
+        camera.setViewOffset(
+            fullWidth || camera.view.fullWidth,
+            fullHeight || camera.view.fullHeight,
+            x || camera.view.offsetX,
+            y || camera.view.offsetY,
+            width || camera.view.width,
+            height || camera.view.height,
+        );
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+    }
+}
diff --git a/examples-testing/examples/css3d_sprites.ts b/examples-testing/examples/css3d_sprites.ts
new file mode 100644
index 000000000..dfe24e79d
--- /dev/null
+++ b/examples-testing/examples/css3d_sprites.ts
@@ -0,0 +1,157 @@
+import * as THREE from 'three';
+
+import TWEEN from 'three/addons/libs/tween.module.js';
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
+
+let camera, scene, renderer;
+let controls;
+
+const particlesTotal = 512;
+const positions = [];
+const objects = [];
+let current = 0;
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.set(600, 400, 1500);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+
+    const image = document.createElement('img');
+    image.addEventListener('load', function () {
+        for (let i = 0; i < particlesTotal; i++) {
+            const object = new CSS3DSprite(image.cloneNode());
+            (object.position.x = Math.random() * 4000 - 2000),
+                (object.position.y = Math.random() * 4000 - 2000),
+                (object.position.z = Math.random() * 4000 - 2000);
+            scene.add(object);
+
+            objects.push(object);
+        }
+
+        transition();
+    });
+    image.src = 'textures/sprite.png';
+
+    // Plane
+
+    const amountX = 16;
+    const amountZ = 32;
+    const separationPlane = 150;
+    const offsetX = ((amountX - 1) * separationPlane) / 2;
+    const offsetZ = ((amountZ - 1) * separationPlane) / 2;
+
+    for (let i = 0; i < particlesTotal; i++) {
+        const x = (i % amountX) * separationPlane;
+        const z = Math.floor(i / amountX) * separationPlane;
+        const y = (Math.sin(x * 0.5) + Math.sin(z * 0.5)) * 200;
+
+        positions.push(x - offsetX, y, z - offsetZ);
+    }
+
+    // Cube
+
+    const amount = 8;
+    const separationCube = 150;
+    const offset = ((amount - 1) * separationCube) / 2;
+
+    for (let i = 0; i < particlesTotal; i++) {
+        const x = (i % amount) * separationCube;
+        const y = Math.floor((i / amount) % amount) * separationCube;
+        const z = Math.floor(i / (amount * amount)) * separationCube;
+
+        positions.push(x - offset, y - offset, z - offset);
+    }
+
+    // Random
+
+    for (let i = 0; i < particlesTotal; i++) {
+        positions.push(Math.random() * 4000 - 2000, Math.random() * 4000 - 2000, Math.random() * 4000 - 2000);
+    }
+
+    // Sphere
+
+    const radius = 750;
+
+    for (let i = 0; i < particlesTotal; i++) {
+        const phi = Math.acos(-1 + (2 * i) / particlesTotal);
+        const theta = Math.sqrt(particlesTotal * Math.PI) * phi;
+
+        positions.push(
+            radius * Math.cos(theta) * Math.sin(phi),
+            radius * Math.sin(theta) * Math.sin(phi),
+            radius * Math.cos(phi),
+        );
+    }
+
+    //
+
+    renderer = new CSS3DRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.getElementById('container').appendChild(renderer.domElement);
+
+    //
+
+    controls = new TrackballControls(camera, renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function transition() {
+    const offset = current * particlesTotal * 3;
+    const duration = 2000;
+
+    for (let i = 0, j = offset; i < particlesTotal; i++, j += 3) {
+        const object = objects[i];
+
+        new TWEEN.Tween(object.position)
+            .to(
+                {
+                    x: positions[j],
+                    y: positions[j + 1],
+                    z: positions[j + 2],
+                },
+                Math.random() * duration + duration,
+            )
+            .easing(TWEEN.Easing.Exponential.InOut)
+            .start();
+    }
+
+    new TWEEN.Tween(this)
+        .to({}, duration * 3)
+        .onComplete(transition)
+        .start();
+
+    current = (current + 1) % 4;
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    TWEEN.update();
+    controls.update();
+
+    const time = performance.now();
+
+    for (let i = 0, l = objects.length; i < l; i++) {
+        const object = objects[i];
+        const scale = Math.sin((Math.floor(object.position.x) + time) * 0.002) * 0.3 + 1;
+        object.scale.set(scale, scale, scale);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/css3d_youtube.ts b/examples-testing/examples/css3d_youtube.ts
new file mode 100644
index 000000000..62652f87f
--- /dev/null
+++ b/examples-testing/examples/css3d_youtube.ts
@@ -0,0 +1,79 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
+
+let camera, scene, renderer;
+let controls;
+
+function Element(id, x, y, z, ry) {
+    const div = document.createElement('div');
+    div.style.width = '480px';
+    div.style.height = '360px';
+    div.style.backgroundColor = '#000';
+
+    const iframe = document.createElement('iframe');
+    iframe.style.width = '480px';
+    iframe.style.height = '360px';
+    iframe.style.border = '0px';
+    iframe.src = ['https://www.youtube.com/embed/', id, '?rel=0'].join('');
+    div.appendChild(iframe);
+
+    const object = new CSS3DObject(div);
+    object.position.set(x, y, z);
+    object.rotation.y = ry;
+
+    return object;
+}
+
+init();
+animate();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.set(500, 350, 750);
+
+    scene = new THREE.Scene();
+
+    renderer = new CSS3DRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    container.appendChild(renderer.domElement);
+
+    const group = new THREE.Group();
+    group.add(new Element('SJOz3qjfQXU', 0, 0, 240, 0));
+    group.add(new Element('Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2));
+    group.add(new Element('IrydklNpcFI', 0, 0, -240, Math.PI));
+    group.add(new Element('9ubytEsCaS0', -240, 0, 0, -Math.PI / 2));
+    scene.add(group);
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.rotateSpeed = 4;
+
+    window.addEventListener('resize', onWindowResize);
+
+    // Block iframe events when dragging camera
+
+    const blocker = document.getElementById('blocker');
+    blocker.style.display = 'none';
+
+    controls.addEventListener('start', function () {
+        blocker.style.display = '';
+    });
+    controls.addEventListener('end', function () {
+        blocker.style.display = 'none';
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+    controls.update();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/games_fps.ts b/examples-testing/examples/games_fps.ts
new file mode 100644
index 000000000..4c459f9bc
--- /dev/null
+++ b/examples-testing/examples/games_fps.ts
@@ -0,0 +1,372 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { Octree } from 'three/addons/math/Octree.js';
+import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
+
+import { Capsule } from 'three/addons/math/Capsule.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const clock = new THREE.Clock();
+
+const scene = new THREE.Scene();
+scene.background = new THREE.Color(0x88ccee);
+scene.fog = new THREE.Fog(0x88ccee, 0, 50);
+
+const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
+camera.rotation.order = 'YXZ';
+
+const fillLight1 = new THREE.HemisphereLight(0x8dc1de, 0x00668d, 1.5);
+fillLight1.position.set(2, 1, 1);
+scene.add(fillLight1);
+
+const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
+directionalLight.position.set(-5, 25, -1);
+directionalLight.castShadow = true;
+directionalLight.shadow.camera.near = 0.01;
+directionalLight.shadow.camera.far = 500;
+directionalLight.shadow.camera.right = 30;
+directionalLight.shadow.camera.left = -30;
+directionalLight.shadow.camera.top = 30;
+directionalLight.shadow.camera.bottom = -30;
+directionalLight.shadow.mapSize.width = 1024;
+directionalLight.shadow.mapSize.height = 1024;
+directionalLight.shadow.radius = 4;
+directionalLight.shadow.bias = -0.00006;
+scene.add(directionalLight);
+
+const container = document.getElementById('container');
+
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setAnimationLoop(animate);
+renderer.shadowMap.enabled = true;
+renderer.shadowMap.type = THREE.VSMShadowMap;
+renderer.toneMapping = THREE.ACESFilmicToneMapping;
+container.appendChild(renderer.domElement);
+
+const stats = new Stats();
+stats.domElement.style.position = 'absolute';
+stats.domElement.style.top = '0px';
+container.appendChild(stats.domElement);
+
+const GRAVITY = 30;
+
+const NUM_SPHERES = 100;
+const SPHERE_RADIUS = 0.2;
+
+const STEPS_PER_FRAME = 5;
+
+const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5);
+const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xdede8d });
+
+const spheres = [];
+let sphereIdx = 0;
+
+for (let i = 0; i < NUM_SPHERES; i++) {
+    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
+    sphere.castShadow = true;
+    sphere.receiveShadow = true;
+
+    scene.add(sphere);
+
+    spheres.push({
+        mesh: sphere,
+        collider: new THREE.Sphere(new THREE.Vector3(0, -100, 0), SPHERE_RADIUS),
+        velocity: new THREE.Vector3(),
+    });
+}
+
+const worldOctree = new Octree();
+
+const playerCollider = new Capsule(new THREE.Vector3(0, 0.35, 0), new THREE.Vector3(0, 1, 0), 0.35);
+
+const playerVelocity = new THREE.Vector3();
+const playerDirection = new THREE.Vector3();
+
+let playerOnFloor = false;
+let mouseTime = 0;
+
+const keyStates = {};
+
+const vector1 = new THREE.Vector3();
+const vector2 = new THREE.Vector3();
+const vector3 = new THREE.Vector3();
+
+document.addEventListener('keydown', event => {
+    keyStates[event.code] = true;
+});
+
+document.addEventListener('keyup', event => {
+    keyStates[event.code] = false;
+});
+
+container.addEventListener('mousedown', () => {
+    document.body.requestPointerLock();
+
+    mouseTime = performance.now();
+});
+
+document.addEventListener('mouseup', () => {
+    if (document.pointerLockElement !== null) throwBall();
+});
+
+document.body.addEventListener('mousemove', event => {
+    if (document.pointerLockElement === document.body) {
+        camera.rotation.y -= event.movementX / 500;
+        camera.rotation.x -= event.movementY / 500;
+    }
+});
+
+window.addEventListener('resize', onWindowResize);
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function throwBall() {
+    const sphere = spheres[sphereIdx];
+
+    camera.getWorldDirection(playerDirection);
+
+    sphere.collider.center.copy(playerCollider.end).addScaledVector(playerDirection, playerCollider.radius * 1.5);
+
+    // throw the ball with more force if we hold the button longer, and if we move forward
+
+    const impulse = 15 + 30 * (1 - Math.exp((mouseTime - performance.now()) * 0.001));
+
+    sphere.velocity.copy(playerDirection).multiplyScalar(impulse);
+    sphere.velocity.addScaledVector(playerVelocity, 2);
+
+    sphereIdx = (sphereIdx + 1) % spheres.length;
+}
+
+function playerCollisions() {
+    const result = worldOctree.capsuleIntersect(playerCollider);
+
+    playerOnFloor = false;
+
+    if (result) {
+        playerOnFloor = result.normal.y > 0;
+
+        if (!playerOnFloor) {
+            playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));
+        }
+
+        if (result.depth >= 1e-10) {
+            playerCollider.translate(result.normal.multiplyScalar(result.depth));
+        }
+    }
+}
+
+function updatePlayer(deltaTime) {
+    let damping = Math.exp(-4 * deltaTime) - 1;
+
+    if (!playerOnFloor) {
+        playerVelocity.y -= GRAVITY * deltaTime;
+
+        // small air resistance
+        damping *= 0.1;
+    }
+
+    playerVelocity.addScaledVector(playerVelocity, damping);
+
+    const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);
+    playerCollider.translate(deltaPosition);
+
+    playerCollisions();
+
+    camera.position.copy(playerCollider.end);
+}
+
+function playerSphereCollision(sphere) {
+    const center = vector1.addVectors(playerCollider.start, playerCollider.end).multiplyScalar(0.5);
+
+    const sphere_center = sphere.collider.center;
+
+    const r = playerCollider.radius + sphere.collider.radius;
+    const r2 = r * r;
+
+    // approximation: player = 3 spheres
+
+    for (const point of [playerCollider.start, playerCollider.end, center]) {
+        const d2 = point.distanceToSquared(sphere_center);
+
+        if (d2 < r2) {
+            const normal = vector1.subVectors(point, sphere_center).normalize();
+            const v1 = vector2.copy(normal).multiplyScalar(normal.dot(playerVelocity));
+            const v2 = vector3.copy(normal).multiplyScalar(normal.dot(sphere.velocity));
+
+            playerVelocity.add(v2).sub(v1);
+            sphere.velocity.add(v1).sub(v2);
+
+            const d = (r - Math.sqrt(d2)) / 2;
+            sphere_center.addScaledVector(normal, -d);
+        }
+    }
+}
+
+function spheresCollisions() {
+    for (let i = 0, length = spheres.length; i < length; i++) {
+        const s1 = spheres[i];
+
+        for (let j = i + 1; j < length; j++) {
+            const s2 = spheres[j];
+
+            const d2 = s1.collider.center.distanceToSquared(s2.collider.center);
+            const r = s1.collider.radius + s2.collider.radius;
+            const r2 = r * r;
+
+            if (d2 < r2) {
+                const normal = vector1.subVectors(s1.collider.center, s2.collider.center).normalize();
+                const v1 = vector2.copy(normal).multiplyScalar(normal.dot(s1.velocity));
+                const v2 = vector3.copy(normal).multiplyScalar(normal.dot(s2.velocity));
+
+                s1.velocity.add(v2).sub(v1);
+                s2.velocity.add(v1).sub(v2);
+
+                const d = (r - Math.sqrt(d2)) / 2;
+
+                s1.collider.center.addScaledVector(normal, d);
+                s2.collider.center.addScaledVector(normal, -d);
+            }
+        }
+    }
+}
+
+function updateSpheres(deltaTime) {
+    spheres.forEach(sphere => {
+        sphere.collider.center.addScaledVector(sphere.velocity, deltaTime);
+
+        const result = worldOctree.sphereIntersect(sphere.collider);
+
+        if (result) {
+            sphere.velocity.addScaledVector(result.normal, -result.normal.dot(sphere.velocity) * 1.5);
+            sphere.collider.center.add(result.normal.multiplyScalar(result.depth));
+        } else {
+            sphere.velocity.y -= GRAVITY * deltaTime;
+        }
+
+        const damping = Math.exp(-1.5 * deltaTime) - 1;
+        sphere.velocity.addScaledVector(sphere.velocity, damping);
+
+        playerSphereCollision(sphere);
+    });
+
+    spheresCollisions();
+
+    for (const sphere of spheres) {
+        sphere.mesh.position.copy(sphere.collider.center);
+    }
+}
+
+function getForwardVector() {
+    camera.getWorldDirection(playerDirection);
+    playerDirection.y = 0;
+    playerDirection.normalize();
+
+    return playerDirection;
+}
+
+function getSideVector() {
+    camera.getWorldDirection(playerDirection);
+    playerDirection.y = 0;
+    playerDirection.normalize();
+    playerDirection.cross(camera.up);
+
+    return playerDirection;
+}
+
+function controls(deltaTime) {
+    // gives a bit of air control
+    const speedDelta = deltaTime * (playerOnFloor ? 25 : 8);
+
+    if (keyStates['KeyW']) {
+        playerVelocity.add(getForwardVector().multiplyScalar(speedDelta));
+    }
+
+    if (keyStates['KeyS']) {
+        playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta));
+    }
+
+    if (keyStates['KeyA']) {
+        playerVelocity.add(getSideVector().multiplyScalar(-speedDelta));
+    }
+
+    if (keyStates['KeyD']) {
+        playerVelocity.add(getSideVector().multiplyScalar(speedDelta));
+    }
+
+    if (playerOnFloor) {
+        if (keyStates['Space']) {
+            playerVelocity.y = 15;
+        }
+    }
+}
+
+const loader = new GLTFLoader().setPath('./models/gltf/');
+
+loader.load('collision-world.glb', gltf => {
+    scene.add(gltf.scene);
+
+    worldOctree.fromGraphNode(gltf.scene);
+
+    gltf.scene.traverse(child => {
+        if (child.isMesh) {
+            child.castShadow = true;
+            child.receiveShadow = true;
+
+            if (child.material.map) {
+                child.material.map.anisotropy = 4;
+            }
+        }
+    });
+
+    const helper = new OctreeHelper(worldOctree);
+    helper.visible = false;
+    scene.add(helper);
+
+    const gui = new GUI({ width: 200 });
+    gui.add({ debug: false }, 'debug').onChange(function (value) {
+        helper.visible = value;
+    });
+});
+
+function teleportPlayerIfOob() {
+    if (camera.position.y <= -25) {
+        playerCollider.start.set(0, 0.35, 0);
+        playerCollider.end.set(0, 1, 0);
+        playerCollider.radius = 0.35;
+        camera.position.copy(playerCollider.end);
+        camera.rotation.set(0, 0, 0);
+    }
+}
+
+function animate() {
+    const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;
+
+    // we look for collisions in substeps to mitigate the risk of
+    // an object traversing another too quickly for detection.
+
+    for (let i = 0; i < STEPS_PER_FRAME; i++) {
+        controls(deltaTime);
+
+        updatePlayer(deltaTime);
+
+        updateSpheres(deltaTime);
+
+        teleportPlayerIfOob();
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/misc_animation_groups.ts b/examples-testing/examples/misc_animation_groups.ts
new file mode 100644
index 000000000..33fc41997
--- /dev/null
+++ b/examples-testing/examples/misc_animation_groups.ts
@@ -0,0 +1,125 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let stats, clock;
+let scene, camera, renderer, mixer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(50, 50, 100);
+    camera.lookAt(scene.position);
+
+    // all objects of this animation group share a common animation state
+
+    const animationGroup = new THREE.AnimationObjectGroup();
+
+    //
+
+    const geometry = new THREE.BoxGeometry(5, 5, 5);
+    const material = new THREE.MeshBasicMaterial({ transparent: true });
+
+    //
+
+    for (let i = 0; i < 5; i++) {
+        for (let j = 0; j < 5; j++) {
+            const mesh = new THREE.Mesh(geometry, material);
+
+            mesh.position.x = 32 - 16 * i;
+            mesh.position.y = 0;
+            mesh.position.z = 32 - 16 * j;
+
+            scene.add(mesh);
+            animationGroup.add(mesh);
+        }
+    }
+
+    // create some keyframe tracks
+
+    const xAxis = new THREE.Vector3(1, 0, 0);
+    const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
+    const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
+    const quaternionKF = new THREE.QuaternionKeyframeTrack(
+        '.quaternion',
+        [0, 1, 2],
+        [
+            qInitial.x,
+            qInitial.y,
+            qInitial.z,
+            qInitial.w,
+            qFinal.x,
+            qFinal.y,
+            qFinal.z,
+            qFinal.w,
+            qInitial.x,
+            qInitial.y,
+            qInitial.z,
+            qInitial.w,
+        ],
+    );
+
+    const colorKF = new THREE.ColorKeyframeTrack(
+        '.material.color',
+        [0, 1, 2],
+        [1, 0, 0, 0, 1, 0, 0, 0, 1],
+        THREE.InterpolateDiscrete,
+    );
+    const opacityKF = new THREE.NumberKeyframeTrack('.material.opacity', [0, 1, 2], [1, 0, 1]);
+
+    // create clip
+
+    const clip = new THREE.AnimationClip('default', 3, [quaternionKF, colorKF, opacityKF]);
+
+    // apply the animation group to the mixer as the root object
+
+    mixer = new THREE.AnimationMixer(animationGroup);
+
+    const clipAction = mixer.clipAction(clip);
+    clipAction.play();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    clock = new THREE.Clock();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) {
+        mixer.update(delta);
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/misc_animation_keys.ts b/examples-testing/examples/misc_animation_keys.ts
new file mode 100644
index 000000000..e2f141f91
--- /dev/null
+++ b/examples-testing/examples/misc_animation_keys.ts
@@ -0,0 +1,129 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let stats, clock;
+let scene, camera, renderer, mixer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(25, 25, 50);
+    camera.lookAt(scene.position);
+
+    //
+
+    const axesHelper = new THREE.AxesHelper(10);
+    scene.add(axesHelper);
+
+    //
+
+    const geometry = new THREE.BoxGeometry(5, 5, 5);
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true });
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    // create a keyframe track (i.e. a timed sequence of keyframes) for each animated property
+    // Note: the keyframe track type should correspond to the type of the property being animated
+
+    // POSITION
+    const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 30, 0, 0, 0, 0, 0]);
+
+    // SCALE
+    const scaleKF = new THREE.VectorKeyframeTrack('.scale', [0, 1, 2], [1, 1, 1, 2, 2, 2, 1, 1, 1]);
+
+    // ROTATION
+    // Rotation should be performed using quaternions, using a THREE.QuaternionKeyframeTrack
+    // Interpolating Euler angles (.rotation property) can be problematic and is currently not supported
+
+    // set up rotation about x axis
+    const xAxis = new THREE.Vector3(1, 0, 0);
+
+    const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
+    const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
+    const quaternionKF = new THREE.QuaternionKeyframeTrack(
+        '.quaternion',
+        [0, 1, 2],
+        [
+            qInitial.x,
+            qInitial.y,
+            qInitial.z,
+            qInitial.w,
+            qFinal.x,
+            qFinal.y,
+            qFinal.z,
+            qFinal.w,
+            qInitial.x,
+            qInitial.y,
+            qInitial.z,
+            qInitial.w,
+        ],
+    );
+
+    // COLOR
+    const colorKF = new THREE.ColorKeyframeTrack(
+        '.material.color',
+        [0, 1, 2],
+        [1, 0, 0, 0, 1, 0, 0, 0, 1],
+        THREE.InterpolateDiscrete,
+    );
+
+    // OPACITY
+    const opacityKF = new THREE.NumberKeyframeTrack('.material.opacity', [0, 1, 2], [1, 0, 1]);
+
+    // create an animation sequence with the tracks
+    // If a negative time value is passed, the duration will be calculated from the times of the passed tracks array
+    const clip = new THREE.AnimationClip('Action', 3, [scaleKF, positionKF, quaternionKF, colorKF, opacityKF]);
+
+    // setup the THREE.AnimationMixer
+    mixer = new THREE.AnimationMixer(mesh);
+
+    // create a ClipAction and set it to play
+    const clipAction = mixer.clipAction(clip);
+    clipAction.play();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    clock = new THREE.Clock();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) {
+        mixer.update(delta);
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/misc_boxselection.ts b/examples-testing/examples/misc_boxselection.ts
new file mode 100644
index 000000000..e7079c405
--- /dev/null
+++ b/examples-testing/examples/misc_boxselection.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { SelectionBox } from 'three/addons/interactive/SelectionBox.js';
+import { SelectionHelper } from 'three/addons/interactive/SelectionHelper.js';
+
+let container, stats;
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 500);
+    camera.position.z = 50;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    scene.add(new THREE.AmbientLight(0xaaaaaa));
+
+    const light = new THREE.SpotLight(0xffffff, 10000);
+    light.position.set(0, 25, 50);
+    light.angle = Math.PI / 5;
+
+    light.castShadow = true;
+    light.shadow.camera.near = 10;
+    light.shadow.camera.far = 100;
+    light.shadow.mapSize.width = 1024;
+    light.shadow.mapSize.height = 1024;
+
+    scene.add(light);
+
+    const geometry = new THREE.BoxGeometry();
+
+    for (let i = 0; i < 200; i++) {
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
+
+        object.position.x = Math.random() * 80 - 40;
+        object.position.y = Math.random() * 45 - 25;
+        object.position.z = Math.random() * 45 - 25;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() * 2 + 1;
+        object.scale.y = Math.random() * 2 + 1;
+        object.scale.z = Math.random() * 2 + 1;
+
+        object.castShadow = true;
+        object.receiveShadow = true;
+
+        scene.add(object);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFShadowMap;
+
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
+
+const selectionBox = new SelectionBox(camera, scene);
+const helper = new SelectionHelper(renderer, 'selectBox');
+
+document.addEventListener('pointerdown', function (event) {
+    for (const item of selectionBox.collection) {
+        item.material.emissive.set(0x000000);
+    }
+
+    selectionBox.startPoint.set(
+        (event.clientX / window.innerWidth) * 2 - 1,
+        -(event.clientY / window.innerHeight) * 2 + 1,
+        0.5,
+    );
+});
+
+document.addEventListener('pointermove', function (event) {
+    if (helper.isDown) {
+        for (let i = 0; i < selectionBox.collection.length; i++) {
+            selectionBox.collection[i].material.emissive.set(0x000000);
+        }
+
+        selectionBox.endPoint.set(
+            (event.clientX / window.innerWidth) * 2 - 1,
+            -(event.clientY / window.innerHeight) * 2 + 1,
+            0.5,
+        );
+
+        const allSelected = selectionBox.select();
+
+        for (let i = 0; i < allSelected.length; i++) {
+            allSelected[i].material.emissive.set(0xffffff);
+        }
+    }
+});
+
+document.addEventListener('pointerup', function (event) {
+    selectionBox.endPoint.set(
+        (event.clientX / window.innerWidth) * 2 - 1,
+        -(event.clientY / window.innerHeight) * 2 + 1,
+        0.5,
+    );
+
+    const allSelected = selectionBox.select();
+
+    for (let i = 0; i < allSelected.length; i++) {
+        allSelected[i].material.emissive.set(0xffffff);
+    }
+});
diff --git a/examples-testing/examples/misc_controls_arcball.ts b/examples-testing/examples/misc_controls_arcball.ts
new file mode 100644
index 000000000..fbef33189
--- /dev/null
+++ b/examples-testing/examples/misc_controls_arcball.ts
@@ -0,0 +1,210 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { ArcballControls } from 'three/addons/controls/ArcballControls.js';
+
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+const cameras = ['Orthographic', 'Perspective'];
+const cameraType = { type: 'Perspective' };
+
+const perspectiveDistance = 2.5;
+const orthographicDistance = 120;
+let camera, controls, scene, renderer, gui;
+let folderOptions, folderAnimations;
+
+const arcballGui = {
+    gizmoVisible: true,
+
+    setArcballControls: function () {
+        controls = new ArcballControls(camera, renderer.domElement, scene);
+        controls.addEventListener('change', render);
+
+        this.gizmoVisible = true;
+
+        this.populateGui();
+    },
+
+    populateGui: function () {
+        folderOptions.add(controls, 'enabled').name('Enable controls');
+        folderOptions.add(controls, 'enableGrid').name('Enable Grid');
+        folderOptions.add(controls, 'enableRotate').name('Enable rotate');
+        folderOptions.add(controls, 'enablePan').name('Enable pan');
+        folderOptions.add(controls, 'enableZoom').name('Enable zoom');
+        folderOptions.add(controls, 'cursorZoom').name('Cursor zoom');
+        folderOptions.add(controls, 'adjustNearFar').name('adjust near/far');
+        folderOptions.add(controls, 'scaleFactor', 1.1, 10, 0.1).name('Scale factor');
+        folderOptions.add(controls, 'minDistance', 0, 50, 0.5).name('Min distance');
+        folderOptions.add(controls, 'maxDistance', 0, 50, 0.5).name('Max distance');
+        folderOptions.add(controls, 'minZoom', 0, 50, 0.5).name('Min zoom');
+        folderOptions.add(controls, 'maxZoom', 0, 50, 0.5).name('Max zoom');
+        folderOptions
+            .add(arcballGui, 'gizmoVisible')
+            .name('Show gizmos')
+            .onChange(function () {
+                controls.setGizmosVisible(arcballGui.gizmoVisible);
+            });
+        folderOptions.add(controls, 'copyState').name('Copy state(ctrl+c)');
+        folderOptions.add(controls, 'pasteState').name('Paste state(ctrl+v)');
+        folderOptions.add(controls, 'reset').name('Reset');
+        folderAnimations.add(controls, 'enableAnimations').name('Enable anim.');
+        folderAnimations.add(controls, 'dampingFactor', 0, 100, 1).name('Damping');
+        folderAnimations.add(controls, 'wMax', 0, 100, 1).name('Angular spd');
+    },
+};
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    renderer.toneMappingExposure = 3;
+    renderer.domElement.style.background = 'linear-gradient( 180deg, rgba( 0,0,0,1 ) 0%, rgba( 128,128,255,1 ) 100% )';
+    container.appendChild(renderer.domElement);
+
+    //
+
+    scene = new THREE.Scene();
+
+    camera = makePerspectiveCamera();
+    camera.position.set(0, 0, perspectiveDistance);
+
+    const material = new THREE.MeshStandardMaterial();
+
+    new OBJLoader().setPath('models/obj/cerberus/').load('Cerberus.obj', function (group) {
+        const textureLoader = new THREE.TextureLoader().setPath('models/obj/cerberus/');
+
+        material.roughness = 1;
+        material.metalness = 1;
+
+        const diffuseMap = textureLoader.load('Cerberus_A.jpg', render);
+        diffuseMap.colorSpace = THREE.SRGBColorSpace;
+        material.map = diffuseMap;
+
+        material.metalnessMap = material.roughnessMap = textureLoader.load('Cerberus_RM.jpg', render);
+        material.normalMap = textureLoader.load('Cerberus_N.jpg', render);
+
+        material.map.wrapS = THREE.RepeatWrapping;
+        material.roughnessMap.wrapS = THREE.RepeatWrapping;
+        material.metalnessMap.wrapS = THREE.RepeatWrapping;
+        material.normalMap.wrapS = THREE.RepeatWrapping;
+
+        group.traverse(function (child) {
+            if (child.isMesh) {
+                child.material = material;
+            }
+        });
+
+        group.rotation.y = Math.PI / 2;
+        group.position.x += 0.25;
+        scene.add(group);
+        render();
+
+        new RGBELoader().setPath('textures/equirectangular/').load('venice_sunset_1k.hdr', function (hdrEquirect) {
+            hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
+
+            scene.environment = hdrEquirect;
+
+            render();
+        });
+
+        window.addEventListener('keydown', onKeyDown);
+        window.addEventListener('resize', onWindowResize);
+
+        //
+
+        gui = new GUI();
+        gui.add(cameraType, 'type', cameras)
+            .name('Choose Camera')
+            .onChange(function () {
+                setCamera(cameraType.type);
+            });
+
+        folderOptions = gui.addFolder('Arcball parameters');
+        folderAnimations = folderOptions.addFolder('Animations');
+
+        arcballGui.setArcballControls();
+
+        render();
+    });
+}
+
+function makeOrthographicCamera() {
+    const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
+    const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
+
+    const halfW = perspectiveDistance * Math.tan(halfFovH);
+    const halfH = perspectiveDistance * Math.tan(halfFovV);
+    const near = 0.01;
+    const far = 2000;
+    const newCamera = new THREE.OrthographicCamera(-halfW, halfW, halfH, -halfH, near, far);
+    return newCamera;
+}
+
+function makePerspectiveCamera() {
+    const fov = 45;
+    const aspect = window.innerWidth / window.innerHeight;
+    const near = 0.01;
+    const far = 2000;
+    const newCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+    return newCamera;
+}
+
+function onWindowResize() {
+    if (camera.type == 'OrthographicCamera') {
+        const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
+        const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
+
+        const halfW = perspectiveDistance * Math.tan(halfFovH);
+        const halfH = perspectiveDistance * Math.tan(halfFovV);
+        camera.left = -halfW;
+        camera.right = halfW;
+        camera.top = halfH;
+        camera.bottom = -halfH;
+    } else if (camera.type == 'PerspectiveCamera') {
+        camera.aspect = window.innerWidth / window.innerHeight;
+    }
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+function onKeyDown(event) {
+    if (event.key === 'c') {
+        if (event.ctrlKey || event.metaKey) {
+            controls.copyState();
+        }
+    } else if (event.key === 'v') {
+        if (event.ctrlKey || event.metaKey) {
+            controls.pasteState();
+        }
+    }
+}
+
+function setCamera(type) {
+    if (type == 'Orthographic') {
+        camera = makeOrthographicCamera();
+        camera.position.set(0, 0, orthographicDistance);
+    } else if (type == 'Perspective') {
+        camera = makePerspectiveCamera();
+        camera.position.set(0, 0, perspectiveDistance);
+    }
+
+    controls.setCamera(camera);
+
+    render();
+}
diff --git a/examples-testing/examples/misc_controls_drag.ts b/examples-testing/examples/misc_controls_drag.ts
new file mode 100644
index 000000000..b12b0421e
--- /dev/null
+++ b/examples-testing/examples/misc_controls_drag.ts
@@ -0,0 +1,153 @@
+import * as THREE from 'three';
+
+import { DragControls } from 'three/addons/controls/DragControls.js';
+
+let container;
+let camera, scene, renderer;
+let controls, group;
+let enableSelection = false;
+
+const objects = [];
+
+const mouse = new THREE.Vector2(),
+    raycaster = new THREE.Raycaster();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 500);
+    camera.position.z = 25;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    scene.add(new THREE.AmbientLight(0xaaaaaa));
+
+    const light = new THREE.SpotLight(0xffffff, 10000);
+    light.position.set(0, 25, 50);
+    light.angle = Math.PI / 9;
+
+    light.castShadow = true;
+    light.shadow.camera.near = 10;
+    light.shadow.camera.far = 100;
+    light.shadow.mapSize.width = 1024;
+    light.shadow.mapSize.height = 1024;
+
+    scene.add(light);
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const geometry = new THREE.BoxGeometry();
+
+    for (let i = 0; i < 200; i++) {
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
+
+        object.position.x = Math.random() * 30 - 15;
+        object.position.y = Math.random() * 15 - 7.5;
+        object.position.z = Math.random() * 20 - 10;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() * 2 + 1;
+        object.scale.y = Math.random() * 2 + 1;
+        object.scale.z = Math.random() * 2 + 1;
+
+        object.castShadow = true;
+        object.receiveShadow = true;
+
+        scene.add(object);
+
+        objects.push(object);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFShadowMap;
+
+    container.appendChild(renderer.domElement);
+
+    controls = new DragControls([...objects], camera, renderer.domElement);
+    controls.rotateSpeed = 2;
+    controls.addEventListener('drag', render);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    document.addEventListener('click', onClick);
+    window.addEventListener('keydown', onKeyDown);
+    window.addEventListener('keyup', onKeyUp);
+
+    render();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function onKeyDown(event) {
+    enableSelection = event.keyCode === 16 ? true : false;
+
+    if (event.keyCode === 77) {
+        controls.touches.ONE = controls.touches.ONE === THREE.TOUCH.PAN ? THREE.TOUCH.ROTATE : THREE.TOUCH.PAN;
+    }
+}
+
+function onKeyUp() {
+    enableSelection = false;
+}
+
+function onClick(event) {
+    event.preventDefault();
+
+    if (enableSelection === true) {
+        const draggableObjects = controls.objects;
+        draggableObjects.length = 0;
+
+        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+        raycaster.setFromCamera(mouse, camera);
+
+        const intersections = raycaster.intersectObjects(objects, true);
+
+        if (intersections.length > 0) {
+            const object = intersections[0].object;
+
+            if (group.children.includes(object) === true) {
+                object.material.emissive.set(0x000000);
+                scene.attach(object);
+            } else {
+                object.material.emissive.set(0xaaaaaa);
+                group.attach(object);
+            }
+
+            controls.transformGroup = true;
+            draggableObjects.push(group);
+        }
+
+        if (group.children.length === 0) {
+            controls.transformGroup = false;
+            draggableObjects.push(...objects);
+        }
+    }
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_controls_fly.ts b/examples-testing/examples/misc_controls_fly.ts
new file mode 100644
index 000000000..5b25c4895
--- /dev/null
+++ b/examples-testing/examples/misc_controls_fly.ts
@@ -0,0 +1,214 @@
+import * as THREE from 'three';
+import { pass, film } from 'three/tsl';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { FlyControls } from 'three/addons/controls/FlyControls.js';
+
+const radius = 6371;
+const tilt = 0.41;
+const rotationSpeed = 0.02;
+
+const cloudsScale = 1.005;
+const moonScale = 0.23;
+
+const MARGIN = 0;
+let SCREEN_HEIGHT = window.innerHeight - MARGIN * 2;
+let SCREEN_WIDTH = window.innerWidth;
+
+let camera, controls, scene, renderer, stats;
+let geometry, meshPlanet, meshClouds, meshMoon;
+let dirLight;
+
+let postProcessing;
+
+const textureLoader = new THREE.TextureLoader();
+
+let d, dPlanet, dMoon;
+const dMoonVec = new THREE.Vector3();
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(25, SCREEN_WIDTH / SCREEN_HEIGHT, 50, 1e7);
+    camera.position.z = radius * 5;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.FogExp2(0x000000, 0.00000025);
+
+    dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(-1, 0, 1).normalize();
+    scene.add(dirLight);
+
+    const materialNormalMap = new THREE.MeshPhongMaterial({
+        specular: 0x7c7c7c,
+        shininess: 15,
+        map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),
+        specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),
+        normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),
+
+        // y scale is negated to compensate for normal map handedness.
+        normalScale: new THREE.Vector2(0.85, -0.85),
+    });
+    materialNormalMap.map.colorSpace = THREE.SRGBColorSpace;
+
+    // planet
+
+    geometry = new THREE.SphereGeometry(radius, 100, 50);
+
+    meshPlanet = new THREE.Mesh(geometry, materialNormalMap);
+    meshPlanet.rotation.y = 0;
+    meshPlanet.rotation.z = tilt;
+    scene.add(meshPlanet);
+
+    // clouds
+
+    const materialClouds = new THREE.MeshLambertMaterial({
+        map: textureLoader.load('textures/planets/earth_clouds_1024.png'),
+        transparent: true,
+    });
+    materialClouds.map.colorSpace = THREE.SRGBColorSpace;
+
+    meshClouds = new THREE.Mesh(geometry, materialClouds);
+    meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale);
+    meshClouds.rotation.z = tilt;
+    scene.add(meshClouds);
+
+    // moon
+
+    const materialMoon = new THREE.MeshPhongMaterial({
+        map: textureLoader.load('textures/planets/moon_1024.jpg'),
+    });
+    materialMoon.map.colorSpace = THREE.SRGBColorSpace;
+
+    meshMoon = new THREE.Mesh(geometry, materialMoon);
+    meshMoon.position.set(radius * 5, 0, 0);
+    meshMoon.scale.set(moonScale, moonScale, moonScale);
+    scene.add(meshMoon);
+
+    // stars
+
+    const r = radius,
+        starsGeometry = [new THREE.BufferGeometry(), new THREE.BufferGeometry()];
+
+    const vertices1 = [];
+    const vertices2 = [];
+
+    const vertex = new THREE.Vector3();
+
+    for (let i = 0; i < 250; i++) {
+        vertex.x = Math.random() * 2 - 1;
+        vertex.y = Math.random() * 2 - 1;
+        vertex.z = Math.random() * 2 - 1;
+        vertex.multiplyScalar(r);
+
+        vertices1.push(vertex.x, vertex.y, vertex.z);
+    }
+
+    for (let i = 0; i < 1500; i++) {
+        vertex.x = Math.random() * 2 - 1;
+        vertex.y = Math.random() * 2 - 1;
+        vertex.z = Math.random() * 2 - 1;
+        vertex.multiplyScalar(r);
+
+        vertices2.push(vertex.x, vertex.y, vertex.z);
+    }
+
+    starsGeometry[0].setAttribute('position', new THREE.Float32BufferAttribute(vertices1, 3));
+    starsGeometry[1].setAttribute('position', new THREE.Float32BufferAttribute(vertices2, 3));
+
+    const starsMaterials = [
+        new THREE.PointsMaterial({ color: 0x9c9c9c }),
+        new THREE.PointsMaterial({ color: 0x838383 }),
+        new THREE.PointsMaterial({ color: 0x5a5a5a }),
+    ];
+
+    for (let i = 10; i < 30; i++) {
+        const stars = new THREE.Points(starsGeometry[i % 2], starsMaterials[i % 3]);
+
+        stars.rotation.x = Math.random() * 6;
+        stars.rotation.y = Math.random() * 6;
+        stars.rotation.z = Math.random() * 6;
+        stars.scale.setScalar(i * 10);
+
+        stars.matrixAutoUpdate = false;
+        stars.updateMatrix();
+
+        scene.add(stars);
+    }
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    controls = new FlyControls(camera, renderer.domElement);
+
+    controls.movementSpeed = 1000;
+    controls.domElement = renderer.domElement;
+    controls.rollSpeed = Math.PI / 24;
+    controls.autoForward = false;
+    controls.dragToLook = false;
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // postprocessing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    const scenePassColor = scenePass.getTextureNode();
+
+    postProcessing.outputNode = film(scenePassColor);
+}
+
+function onWindowResize() {
+    SCREEN_HEIGHT = window.innerHeight;
+    SCREEN_WIDTH = window.innerWidth;
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    // rotate the planet and clouds
+
+    const delta = clock.getDelta();
+
+    meshPlanet.rotation.y += rotationSpeed * delta;
+    meshClouds.rotation.y += 1.25 * rotationSpeed * delta;
+
+    // slow down as we approach the surface
+
+    dPlanet = camera.position.length();
+
+    dMoonVec.subVectors(camera.position, meshMoon.position);
+    dMoon = dMoonVec.length();
+
+    if (dMoon < dPlanet) {
+        d = dMoon - radius * moonScale * 1.01;
+    } else {
+        d = dPlanet - radius * 1.01;
+    }
+
+    controls.movementSpeed = 0.33 * d;
+    controls.update(delta);
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/misc_controls_map.ts b/examples-testing/examples/misc_controls_map.ts
new file mode 100644
index 000000000..2f52190cf
--- /dev/null
+++ b/examples-testing/examples/misc_controls_map.ts
@@ -0,0 +1,98 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { MapControls } from 'three/addons/controls/MapControls.js';
+
+let camera, controls, scene, renderer;
+
+init();
+//render(); // remove when using animation loop
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xcccccc);
+    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 200, -400);
+
+    // controls
+
+    controls = new MapControls(camera, renderer.domElement);
+
+    //controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
+
+    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
+    controls.dampingFactor = 0.05;
+
+    controls.screenSpacePanning = false;
+
+    controls.minDistance = 100;
+    controls.maxDistance = 500;
+
+    controls.maxPolarAngle = Math.PI / 2;
+
+    // world
+
+    const geometry = new THREE.BoxGeometry();
+    geometry.translate(0, 0.5, 0);
+    const material = new THREE.MeshPhongMaterial({ color: 0xeeeeee, flatShading: true });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 1600 - 800;
+        mesh.position.y = 0;
+        mesh.position.z = Math.random() * 1600 - 800;
+        mesh.scale.x = 20;
+        mesh.scale.y = Math.random() * 80 + 10;
+        mesh.scale.z = 20;
+        mesh.updateMatrix();
+        mesh.matrixAutoUpdate = false;
+        scene.add(mesh);
+    }
+
+    // lights
+
+    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight1.position.set(1, 1, 1);
+    scene.add(dirLight1);
+
+    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
+    dirLight2.position.set(-1, -1, -1);
+    scene.add(dirLight2);
+
+    const ambientLight = new THREE.AmbientLight(0x555555);
+    scene.add(ambientLight);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+    gui.add(controls, 'zoomToCursor');
+    gui.add(controls, 'screenSpacePanning');
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_controls_orbit.ts b/examples-testing/examples/misc_controls_orbit.ts
new file mode 100644
index 000000000..186e216cb
--- /dev/null
+++ b/examples-testing/examples/misc_controls_orbit.ts
@@ -0,0 +1,89 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, controls, scene, renderer;
+
+init();
+//render(); // remove when using animation loop
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xcccccc);
+    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(400, 200, 0);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.listenToKeyEvents(window); // optional
+
+    //controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
+
+    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
+    controls.dampingFactor = 0.05;
+
+    controls.screenSpacePanning = false;
+
+    controls.minDistance = 100;
+    controls.maxDistance = 500;
+
+    controls.maxPolarAngle = Math.PI / 2;
+
+    // world
+
+    const geometry = new THREE.ConeGeometry(10, 30, 4, 1);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 1600 - 800;
+        mesh.position.y = 0;
+        mesh.position.z = Math.random() * 1600 - 800;
+        mesh.updateMatrix();
+        mesh.matrixAutoUpdate = false;
+        scene.add(mesh);
+    }
+
+    // lights
+
+    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight1.position.set(1, 1, 1);
+    scene.add(dirLight1);
+
+    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
+    dirLight2.position.set(-1, -1, -1);
+    scene.add(dirLight2);
+
+    const ambientLight = new THREE.AmbientLight(0x555555);
+    scene.add(ambientLight);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_controls_pointerlock.ts b/examples-testing/examples/misc_controls_pointerlock.ts
new file mode 100644
index 000000000..0b6fcc516
--- /dev/null
+++ b/examples-testing/examples/misc_controls_pointerlock.ts
@@ -0,0 +1,245 @@
+import * as THREE from 'three';
+
+import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
+
+let camera, scene, renderer, controls;
+
+const objects = [];
+
+let raycaster;
+
+let moveForward = false;
+let moveBackward = false;
+let moveLeft = false;
+let moveRight = false;
+let canJump = false;
+
+let prevTime = performance.now();
+const velocity = new THREE.Vector3();
+const direction = new THREE.Vector3();
+const vertex = new THREE.Vector3();
+const color = new THREE.Color();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.y = 10;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+    scene.fog = new THREE.Fog(0xffffff, 0, 750);
+
+    const light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 2.5);
+    light.position.set(0.5, 1, 0.75);
+    scene.add(light);
+
+    controls = new PointerLockControls(camera, document.body);
+
+    const blocker = document.getElementById('blocker');
+    const instructions = document.getElementById('instructions');
+
+    instructions.addEventListener('click', function () {
+        controls.lock();
+    });
+
+    controls.addEventListener('lock', function () {
+        instructions.style.display = 'none';
+        blocker.style.display = 'none';
+    });
+
+    controls.addEventListener('unlock', function () {
+        blocker.style.display = 'block';
+        instructions.style.display = '';
+    });
+
+    scene.add(controls.object);
+
+    const onKeyDown = function (event) {
+        switch (event.code) {
+            case 'ArrowUp':
+            case 'KeyW':
+                moveForward = true;
+                break;
+
+            case 'ArrowLeft':
+            case 'KeyA':
+                moveLeft = true;
+                break;
+
+            case 'ArrowDown':
+            case 'KeyS':
+                moveBackward = true;
+                break;
+
+            case 'ArrowRight':
+            case 'KeyD':
+                moveRight = true;
+                break;
+
+            case 'Space':
+                if (canJump === true) velocity.y += 350;
+                canJump = false;
+                break;
+        }
+    };
+
+    const onKeyUp = function (event) {
+        switch (event.code) {
+            case 'ArrowUp':
+            case 'KeyW':
+                moveForward = false;
+                break;
+
+            case 'ArrowLeft':
+            case 'KeyA':
+                moveLeft = false;
+                break;
+
+            case 'ArrowDown':
+            case 'KeyS':
+                moveBackward = false;
+                break;
+
+            case 'ArrowRight':
+            case 'KeyD':
+                moveRight = false;
+                break;
+        }
+    };
+
+    document.addEventListener('keydown', onKeyDown);
+    document.addEventListener('keyup', onKeyUp);
+
+    raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 10);
+
+    // floor
+
+    let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100);
+    floorGeometry.rotateX(-Math.PI / 2);
+
+    // vertex displacement
+
+    let position = floorGeometry.attributes.position;
+
+    for (let i = 0, l = position.count; i < l; i++) {
+        vertex.fromBufferAttribute(position, i);
+
+        vertex.x += Math.random() * 20 - 10;
+        vertex.y += Math.random() * 2;
+        vertex.z += Math.random() * 20 - 10;
+
+        position.setXYZ(i, vertex.x, vertex.y, vertex.z);
+    }
+
+    floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
+
+    position = floorGeometry.attributes.position;
+    const colorsFloor = [];
+
+    for (let i = 0, l = position.count; i < l; i++) {
+        color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
+        colorsFloor.push(color.r, color.g, color.b);
+    }
+
+    floorGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsFloor, 3));
+
+    const floorMaterial = new THREE.MeshBasicMaterial({ vertexColors: true });
+
+    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
+    scene.add(floor);
+
+    // objects
+
+    const boxGeometry = new THREE.BoxGeometry(20, 20, 20).toNonIndexed();
+
+    position = boxGeometry.attributes.position;
+    const colorsBox = [];
+
+    for (let i = 0, l = position.count; i < l; i++) {
+        color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
+        colorsBox.push(color.r, color.g, color.b);
+    }
+
+    boxGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsBox, 3));
+
+    for (let i = 0; i < 500; i++) {
+        const boxMaterial = new THREE.MeshPhongMaterial({ specular: 0xffffff, flatShading: true, vertexColors: true });
+        boxMaterial.color.setHSL(Math.random() * 0.2 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
+
+        const box = new THREE.Mesh(boxGeometry, boxMaterial);
+        box.position.x = Math.floor(Math.random() * 20 - 10) * 20;
+        box.position.y = Math.floor(Math.random() * 20) * 20 + 10;
+        box.position.z = Math.floor(Math.random() * 20 - 10) * 20;
+
+        scene.add(box);
+        objects.push(box);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = performance.now();
+
+    if (controls.isLocked === true) {
+        raycaster.ray.origin.copy(controls.object.position);
+        raycaster.ray.origin.y -= 10;
+
+        const intersections = raycaster.intersectObjects(objects, false);
+
+        const onObject = intersections.length > 0;
+
+        const delta = (time - prevTime) / 1000;
+
+        velocity.x -= velocity.x * 10.0 * delta;
+        velocity.z -= velocity.z * 10.0 * delta;
+
+        velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
+
+        direction.z = Number(moveForward) - Number(moveBackward);
+        direction.x = Number(moveRight) - Number(moveLeft);
+        direction.normalize(); // this ensures consistent movements in all directions
+
+        if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta;
+        if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta;
+
+        if (onObject === true) {
+            velocity.y = Math.max(0, velocity.y);
+            canJump = true;
+        }
+
+        controls.moveRight(-velocity.x * delta);
+        controls.moveForward(-velocity.z * delta);
+
+        controls.object.position.y += velocity.y * delta; // new behavior
+
+        if (controls.object.position.y < 10) {
+            velocity.y = 0;
+            controls.object.position.y = 10;
+
+            canJump = true;
+        }
+    }
+
+    prevTime = time;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_controls_trackball.ts b/examples-testing/examples/misc_controls_trackball.ts
new file mode 100644
index 000000000..b6479e9f6
--- /dev/null
+++ b/examples-testing/examples/misc_controls_trackball.ts
@@ -0,0 +1,134 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+
+let perspectiveCamera, orthographicCamera, controls, scene, renderer, stats;
+
+const params = {
+    orthographicCamera: false,
+};
+
+const frustumSize = 400;
+
+init();
+
+function init() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    perspectiveCamera = new THREE.PerspectiveCamera(60, aspect, 1, 1000);
+    perspectiveCamera.position.z = 500;
+
+    orthographicCamera = new THREE.OrthographicCamera(
+        (frustumSize * aspect) / -2,
+        (frustumSize * aspect) / 2,
+        frustumSize / 2,
+        frustumSize / -2,
+        1,
+        1000,
+    );
+    orthographicCamera.position.z = 500;
+
+    // world
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xcccccc);
+    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
+
+    const geometry = new THREE.ConeGeometry(10, 30, 4, 1);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = (Math.random() - 0.5) * 1000;
+        mesh.position.y = (Math.random() - 0.5) * 1000;
+        mesh.position.z = (Math.random() - 0.5) * 1000;
+        mesh.updateMatrix();
+        mesh.matrixAutoUpdate = false;
+        scene.add(mesh);
+    }
+
+    // lights
+
+    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight1.position.set(1, 1, 1);
+    scene.add(dirLight1);
+
+    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
+    dirLight2.position.set(-1, -1, -1);
+    scene.add(dirLight2);
+
+    const ambientLight = new THREE.AmbientLight(0x555555);
+    scene.add(ambientLight);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'orthographicCamera')
+        .name('use orthographic')
+        .onChange(function (value) {
+            controls.dispose();
+
+            createControls(value ? orthographicCamera : perspectiveCamera);
+        });
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    createControls(perspectiveCamera);
+}
+
+function createControls(camera) {
+    controls = new TrackballControls(camera, renderer.domElement);
+
+    controls.rotateSpeed = 1.0;
+    controls.zoomSpeed = 1.2;
+    controls.panSpeed = 0.8;
+
+    controls.keys = ['KeyA', 'KeyS', 'KeyD'];
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    perspectiveCamera.aspect = aspect;
+    perspectiveCamera.updateProjectionMatrix();
+
+    orthographicCamera.left = (-frustumSize * aspect) / 2;
+    orthographicCamera.right = (frustumSize * aspect) / 2;
+    orthographicCamera.top = frustumSize / 2;
+    orthographicCamera.bottom = -frustumSize / 2;
+    orthographicCamera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+function animate() {
+    controls.update();
+
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const camera = params.orthographicCamera ? orthographicCamera : perspectiveCamera;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_controls_transform.ts b/examples-testing/examples/misc_controls_transform.ts
new file mode 100644
index 000000000..9d14bf7ee
--- /dev/null
+++ b/examples-testing/examples/misc_controls_transform.ts
@@ -0,0 +1,181 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { TransformControls } from 'three/addons/controls/TransformControls.js';
+
+let cameraPersp, cameraOrtho, currentCamera;
+let scene, renderer, control, orbit;
+
+init();
+render();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const aspect = window.innerWidth / window.innerHeight;
+
+    const frustumSize = 5;
+
+    cameraPersp = new THREE.PerspectiveCamera(50, aspect, 0.1, 100);
+    cameraOrtho = new THREE.OrthographicCamera(
+        -frustumSize * aspect,
+        frustumSize * aspect,
+        frustumSize,
+        -frustumSize,
+        0.1,
+        100,
+    );
+    currentCamera = cameraPersp;
+
+    currentCamera.position.set(5, 2.5, 5);
+
+    scene = new THREE.Scene();
+    scene.add(new THREE.GridHelper(5, 10, 0x888888, 0x444444));
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    scene.add(ambientLight);
+
+    const light = new THREE.DirectionalLight(0xffffff, 4);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    const texture = new THREE.TextureLoader().load('textures/crate.gif', render);
+    texture.colorSpace = THREE.SRGBColorSpace;
+    texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
+
+    const geometry = new THREE.BoxGeometry();
+    const material = new THREE.MeshLambertMaterial({ map: texture });
+
+    orbit = new OrbitControls(currentCamera, renderer.domElement);
+    orbit.update();
+    orbit.addEventListener('change', render);
+
+    control = new TransformControls(currentCamera, renderer.domElement);
+    control.addEventListener('change', render);
+
+    control.addEventListener('dragging-changed', function (event) {
+        orbit.enabled = !event.value;
+    });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    control.attach(mesh);
+    scene.add(control);
+
+    window.addEventListener('resize', onWindowResize);
+
+    window.addEventListener('keydown', function (event) {
+        switch (event.key) {
+            case 'q':
+                control.setSpace(control.space === 'local' ? 'world' : 'local');
+                break;
+
+            case 'Shift':
+                control.setTranslationSnap(1);
+                control.setRotationSnap(THREE.MathUtils.degToRad(15));
+                control.setScaleSnap(0.25);
+                break;
+
+            case 'w':
+                control.setMode('translate');
+                break;
+
+            case 'e':
+                control.setMode('rotate');
+                break;
+
+            case 'r':
+                control.setMode('scale');
+                break;
+
+            case 'c':
+                const position = currentCamera.position.clone();
+
+                currentCamera = currentCamera.isPerspectiveCamera ? cameraOrtho : cameraPersp;
+                currentCamera.position.copy(position);
+
+                orbit.object = currentCamera;
+                control.camera = currentCamera;
+
+                currentCamera.lookAt(orbit.target.x, orbit.target.y, orbit.target.z);
+                onWindowResize();
+                break;
+
+            case 'v':
+                const randomFoV = Math.random() + 0.1;
+                const randomZoom = Math.random() + 0.1;
+
+                cameraPersp.fov = randomFoV * 160;
+                cameraOrtho.bottom = -randomFoV * 500;
+                cameraOrtho.top = randomFoV * 500;
+
+                cameraPersp.zoom = randomZoom * 5;
+                cameraOrtho.zoom = randomZoom * 5;
+                onWindowResize();
+                break;
+
+            case '+':
+            case '=':
+                control.setSize(control.size + 0.1);
+                break;
+
+            case '-':
+            case '_':
+                control.setSize(Math.max(control.size - 0.1, 0.1));
+                break;
+
+            case 'x':
+                control.showX = !control.showX;
+                break;
+
+            case 'y':
+                control.showY = !control.showY;
+                break;
+
+            case 'z':
+                control.showZ = !control.showZ;
+                break;
+
+            case ' ':
+                control.enabled = !control.enabled;
+                break;
+
+            case 'Escape':
+                control.reset();
+                break;
+        }
+    });
+
+    window.addEventListener('keyup', function (event) {
+        switch (event.key) {
+            case 'Shift':
+                control.setTranslationSnap(null);
+                control.setRotationSnap(null);
+                control.setScaleSnap(null);
+                break;
+        }
+    });
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    cameraPersp.aspect = aspect;
+    cameraPersp.updateProjectionMatrix();
+
+    cameraOrtho.left = cameraOrtho.bottom * aspect;
+    cameraOrtho.right = cameraOrtho.top * aspect;
+    cameraOrtho.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, currentCamera);
+}
diff --git a/examples-testing/examples/misc_exporter_draco.ts b/examples-testing/examples/misc_exporter_draco.ts
new file mode 100644
index 000000000..40a62fb18
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_draco.ts
@@ -0,0 +1,117 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, renderer, exporter, mesh;
+
+const params = {
+    export: exportFile,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(4, 2, 4);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
+
+    exporter = new DRACOExporter();
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(0, 20, 10);
+    directionalLight.castShadow = true;
+    directionalLight.shadow.camera.top = 2;
+    directionalLight.shadow.camera.bottom = -2;
+    directionalLight.shadow.camera.left = -2;
+    directionalLight.shadow.camera.right = 2;
+    scene.add(directionalLight);
+
+    // ground
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(40, 40),
+        new THREE.MeshPhongMaterial({ color: 0xbbbbbb, depthWrite: false }),
+    );
+    ground.rotation.x = -Math.PI / 2;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
+    grid.material.opacity = 0.2;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    // export mesh
+
+    const geometry = new THREE.TorusKnotGeometry(0.75, 0.2, 200, 30);
+    const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.castShadow = true;
+    mesh.position.y = 1.5;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 1.5, 0);
+    controls.update();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'export').name('Export DRC');
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
+
+function exportFile() {
+    const result = exporter.parse(mesh);
+    saveArrayBuffer(result, 'file.drc');
+}
+
+const link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link);
+
+function save(blob, filename) {
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+}
+
+function saveArrayBuffer(buffer, filename) {
+    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
+}
diff --git a/examples-testing/examples/misc_exporter_exr.ts b/examples-testing/examples/misc_exporter_exr.ts
new file mode 100644
index 000000000..c239a65fa
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_exr.ts
@@ -0,0 +1,158 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { EXRExporter, ZIP_COMPRESSION, ZIPS_COMPRESSION, NO_COMPRESSION } from 'three/addons/exporters/EXRExporter.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, renderer, exporter, mesh, controls, renderTarget, dataTexture;
+
+const params = {
+    target: 'pmrem',
+    type: 'HalfFloatType',
+    compression: 'ZIP',
+    export: exportFile,
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(10, 0, 0);
+
+    scene = new THREE.Scene();
+
+    exporter = new EXRExporter();
+    const rgbeloader = new RGBELoader();
+
+    //
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    pmremGenerator.compileEquirectangularShader();
+
+    rgbeloader.load('textures/equirectangular/san_giuseppe_bridge_2k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        renderTarget = pmremGenerator.fromEquirectangular(texture);
+        scene.background = renderTarget.texture;
+    });
+
+    createDataTexture();
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.rotateSpeed = -0.25; // negative, to track mouse pointer
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    const input = gui.addFolder('Input');
+    input.add(params, 'target').options(['pmrem', 'data-texture']).onChange(swapScene);
+
+    const options = gui.addFolder('Output Options');
+    options.add(params, 'type').options(['FloatType', 'HalfFloatType']);
+    options.add(params, 'compression').options(['ZIP', 'ZIPS', 'NONE']);
+
+    gui.add(params, 'export').name('Export EXR');
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+    renderer.render(scene, camera);
+}
+
+function createDataTexture() {
+    const normal = new THREE.Vector3();
+    const coord = new THREE.Vector2();
+    const size = 800,
+        radius = 320,
+        factor = (Math.PI * 0.5) / radius;
+    const data = new Float32Array(4 * size * size);
+
+    for (let i = 0; i < size; i++) {
+        for (let j = 0; j < size; j++) {
+            const idx = i * size * 4 + j * 4;
+            coord.set(j, i).subScalar(size / 2);
+
+            if (coord.length() < radius)
+                normal.set(Math.sin(coord.x * factor), Math.sin(coord.y * factor), Math.cos(coord.x * factor));
+            else normal.set(0, 0, 1);
+
+            data[idx + 0] = 0.5 + 0.5 * normal.x;
+            data[idx + 1] = 0.5 + 0.5 * normal.y;
+            data[idx + 2] = 0.5 + 0.5 * normal.z;
+            data[idx + 3] = 1;
+        }
+    }
+
+    dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);
+    dataTexture.needsUpdate = true;
+
+    const material = new THREE.MeshBasicMaterial({ map: dataTexture });
+    const quad = new THREE.PlaneGeometry(50, 50);
+    mesh = new THREE.Mesh(quad, material);
+    mesh.visible = false;
+
+    scene.add(mesh);
+}
+
+function swapScene() {
+    if (params.target == 'pmrem') {
+        camera.position.set(10, 0, 0);
+        controls.enabled = true;
+        scene.background = renderTarget.texture;
+        mesh.visible = false;
+    } else {
+        camera.position.set(0, 0, 70);
+        controls.enabled = false;
+        scene.background = new THREE.Color(0, 0, 0);
+        mesh.visible = true;
+    }
+}
+
+function exportFile() {
+    let result, exportType, exportCompression;
+
+    if (params.type == 'HalfFloatType') exportType = THREE.HalfFloatType;
+    else exportType = THREE.FloatType;
+
+    if (params.compression == 'ZIP') exportCompression = ZIP_COMPRESSION;
+    else if (params.compression == 'ZIPS') exportCompression = ZIPS_COMPRESSION;
+    else exportCompression = NO_COMPRESSION;
+
+    if (params.target == 'pmrem')
+        result = exporter.parse(renderer, renderTarget, { type: exportType, compression: exportCompression });
+    else result = exporter.parse(dataTexture, { type: exportType, compression: exportCompression });
+
+    saveArrayBuffer(result, params.target + '.exr');
+}
+
+function saveArrayBuffer(buffer, filename) {
+    const blob = new Blob([buffer], { type: 'image/x-exr' });
+    const link = document.createElement('a');
+
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+}
diff --git a/examples-testing/examples/misc_exporter_gltf.ts b/examples-testing/examples/misc_exporter_gltf.ts
new file mode 100644
index 000000000..e4172b852
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_gltf.ts
@@ -0,0 +1,507 @@
+import * as THREE from 'three';
+
+import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+function exportGLTF(input) {
+    const gltfExporter = new GLTFExporter();
+
+    const options = {
+        trs: params.trs,
+        onlyVisible: params.onlyVisible,
+        binary: params.binary,
+        maxTextureSize: params.maxTextureSize,
+    };
+    gltfExporter.parse(
+        input,
+        function (result) {
+            if (result instanceof ArrayBuffer) {
+                saveArrayBuffer(result, 'scene.glb');
+            } else {
+                const output = JSON.stringify(result, null, 2);
+                console.log(output);
+                saveString(output, 'scene.gltf');
+            }
+        },
+        function (error) {
+            console.log('An error happened during parsing', error);
+        },
+        options,
+    );
+}
+
+const link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link); // Firefox workaround, see #6594
+
+function save(blob, filename) {
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+
+    // URL.revokeObjectURL( url ); breaks Firefox...
+}
+
+function saveString(text, filename) {
+    save(new Blob([text], { type: 'text/plain' }), filename);
+}
+
+function saveArrayBuffer(buffer, filename) {
+    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
+}
+
+let container;
+
+let camera, object, object2, material, geometry, scene1, scene2, renderer;
+let gridHelper, sphere, model, coffeemat;
+
+const params = {
+    trs: false,
+    onlyVisible: true,
+    binary: false,
+    maxTextureSize: 4096,
+    exportScene1: exportScene1,
+    exportScenes: exportScenes,
+    exportSphere: exportSphere,
+    exportModel: exportModel,
+    exportObjects: exportObjects,
+    exportSceneObject: exportSceneObject,
+    exportCompressedObject: exportCompressedObject,
+};
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // Make linear gradient texture
+
+    const data = new Uint8ClampedArray(100 * 100 * 4);
+
+    for (let y = 0; y < 100; y++) {
+        for (let x = 0; x < 100; x++) {
+            const stride = 4 * (100 * y + x);
+
+            data[stride] = Math.round((255 * y) / 99);
+            data[stride + 1] = Math.round(255 - (255 * y) / 99);
+            data[stride + 2] = 0;
+            data[stride + 3] = 255;
+        }
+    }
+
+    const gradientTexture = new THREE.DataTexture(data, 100, 100, THREE.RGBAFormat);
+    gradientTexture.minFilter = THREE.LinearFilter;
+    gradientTexture.magFilter = THREE.LinearFilter;
+    gradientTexture.needsUpdate = true;
+
+    scene1 = new THREE.Scene();
+    scene1.name = 'Scene1';
+
+    // ---------------------------------------------------------------------
+    // Perspective Camera
+    // ---------------------------------------------------------------------
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(600, 400, 0);
+
+    camera.name = 'PerspectiveCamera';
+    scene1.add(camera);
+
+    // ---------------------------------------------------------------------
+    // Ambient light
+    // ---------------------------------------------------------------------
+    const ambientLight = new THREE.AmbientLight(0xcccccc);
+    ambientLight.name = 'AmbientLight';
+    scene1.add(ambientLight);
+
+    // ---------------------------------------------------------------------
+    // DirectLight
+    // ---------------------------------------------------------------------
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.target.position.set(0, 0, -1);
+    dirLight.add(dirLight.target);
+    dirLight.lookAt(-1, -1, 0);
+    dirLight.name = 'DirectionalLight';
+    scene1.add(dirLight);
+
+    // ---------------------------------------------------------------------
+    // Grid
+    // ---------------------------------------------------------------------
+    gridHelper = new THREE.GridHelper(2000, 20, 0xc1c1c1, 0x8d8d8d);
+    gridHelper.position.y = -50;
+    gridHelper.name = 'Grid';
+    scene1.add(gridHelper);
+
+    // ---------------------------------------------------------------------
+    // Axes
+    // ---------------------------------------------------------------------
+    const axes = new THREE.AxesHelper(500);
+    axes.name = 'AxesHelper';
+    scene1.add(axes);
+
+    // ---------------------------------------------------------------------
+    // Simple geometry with basic material
+    // ---------------------------------------------------------------------
+    // Icosahedron
+    const mapGrid = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    mapGrid.wrapS = mapGrid.wrapT = THREE.RepeatWrapping;
+    mapGrid.colorSpace = THREE.SRGBColorSpace;
+    material = new THREE.MeshBasicMaterial({
+        color: 0xffffff,
+        map: mapGrid,
+    });
+
+    object = new THREE.Mesh(new THREE.IcosahedronGeometry(75, 0), material);
+    object.position.set(-200, 0, 200);
+    object.name = 'Icosahedron';
+    scene1.add(object);
+
+    // Octahedron
+    material = new THREE.MeshBasicMaterial({
+        color: 0x0000ff,
+        wireframe: true,
+    });
+    object = new THREE.Mesh(new THREE.OctahedronGeometry(75, 1), material);
+    object.position.set(0, 0, 200);
+    object.name = 'Octahedron';
+    scene1.add(object);
+
+    // Tetrahedron
+    material = new THREE.MeshBasicMaterial({
+        color: 0xff0000,
+        transparent: true,
+        opacity: 0.5,
+    });
+
+    object = new THREE.Mesh(new THREE.TetrahedronGeometry(75, 0), material);
+    object.position.set(200, 0, 200);
+    object.name = 'Tetrahedron';
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // Buffered geometry primitives
+    // ---------------------------------------------------------------------
+    // Sphere
+    material = new THREE.MeshStandardMaterial({
+        color: 0xffff00,
+        metalness: 0.5,
+        roughness: 1.0,
+        flatShading: true,
+    });
+    material.map = gradientTexture;
+    material.bumpMap = mapGrid;
+    sphere = new THREE.Mesh(new THREE.SphereGeometry(70, 10, 10), material);
+    sphere.position.set(0, 0, 0);
+    sphere.name = 'Sphere';
+    scene1.add(sphere);
+
+    // Cylinder
+    material = new THREE.MeshStandardMaterial({
+        color: 0xff00ff,
+        flatShading: true,
+    });
+    object = new THREE.Mesh(new THREE.CylinderGeometry(10, 80, 100), material);
+    object.position.set(200, 0, 0);
+    object.name = 'Cylinder';
+    scene1.add(object);
+
+    // TorusKnot
+    material = new THREE.MeshStandardMaterial({
+        color: 0xff0000,
+        roughness: 1,
+    });
+    object = new THREE.Mesh(new THREE.TorusKnotGeometry(50, 15, 40, 10), material);
+    object.position.set(-200, 0, 0);
+    object.name = 'Cylinder';
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // Hierarchy
+    // ---------------------------------------------------------------------
+    const mapWood = new THREE.TextureLoader().load('textures/hardwood2_diffuse.jpg');
+    material = new THREE.MeshStandardMaterial({ map: mapWood, side: THREE.DoubleSide });
+
+    object = new THREE.Mesh(new THREE.BoxGeometry(40, 100, 100), material);
+    object.position.set(-200, 0, 400);
+    object.name = 'Cube';
+    scene1.add(object);
+
+    object2 = new THREE.Mesh(new THREE.BoxGeometry(40, 40, 40, 2, 2, 2), material);
+    object2.position.set(0, 0, 50);
+    object2.rotation.set(0, 45, 0);
+    object2.name = 'SubCube';
+    object.add(object2);
+
+    // ---------------------------------------------------------------------
+    // Groups
+    // ---------------------------------------------------------------------
+    const group1 = new THREE.Group();
+    group1.name = 'Group';
+    scene1.add(group1);
+
+    const group2 = new THREE.Group();
+    group2.name = 'subGroup';
+    group2.position.set(0, 50, 0);
+    group1.add(group2);
+
+    object2 = new THREE.Mesh(new THREE.BoxGeometry(30, 30, 30), material);
+    object2.name = 'Cube in group';
+    object2.position.set(0, 0, 400);
+    group2.add(object2);
+
+    // ---------------------------------------------------------------------
+    // THREE.Line Strip
+    // ---------------------------------------------------------------------
+    geometry = new THREE.BufferGeometry();
+    let numPoints = 100;
+    let positions = new Float32Array(numPoints * 3);
+
+    for (let i = 0; i < numPoints; i++) {
+        positions[i * 3] = i;
+        positions[i * 3 + 1] = Math.sin(i / 2) * 20;
+        positions[i * 3 + 2] = 0;
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    object = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xffff00 }));
+    object.position.set(-50, 0, -200);
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // THREE.Line Loop
+    // ---------------------------------------------------------------------
+    geometry = new THREE.BufferGeometry();
+    numPoints = 5;
+    const radius = 70;
+    positions = new Float32Array(numPoints * 3);
+
+    for (let i = 0; i < numPoints; i++) {
+        const s = (i * Math.PI * 2) / numPoints;
+        positions[i * 3] = radius * Math.sin(s);
+        positions[i * 3 + 1] = radius * Math.cos(s);
+        positions[i * 3 + 2] = 0;
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    object = new THREE.LineLoop(geometry, new THREE.LineBasicMaterial({ color: 0xffff00 }));
+    object.position.set(0, 0, -200);
+
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // THREE.Points
+    // ---------------------------------------------------------------------
+    numPoints = 100;
+    const pointsArray = new Float32Array(numPoints * 3);
+    for (let i = 0; i < numPoints; i++) {
+        pointsArray[3 * i] = -50 + Math.random() * 100;
+        pointsArray[3 * i + 1] = Math.random() * 100;
+        pointsArray[3 * i + 2] = -50 + Math.random() * 100;
+    }
+
+    const pointsGeo = new THREE.BufferGeometry();
+    pointsGeo.setAttribute('position', new THREE.BufferAttribute(pointsArray, 3));
+
+    const pointsMaterial = new THREE.PointsMaterial({ color: 0xffff00, size: 5 });
+    const pointCloud = new THREE.Points(pointsGeo, pointsMaterial);
+    pointCloud.name = 'Points';
+    pointCloud.position.set(-200, 0, -200);
+    scene1.add(pointCloud);
+
+    // ---------------------------------------------------------------------
+    // Ortho camera
+    // ---------------------------------------------------------------------
+    const cameraOrtho = new THREE.OrthographicCamera(
+        window.innerWidth / -2,
+        window.innerWidth / 2,
+        window.innerHeight / 2,
+        window.innerHeight / -2,
+        0.1,
+        10,
+    );
+    scene1.add(cameraOrtho);
+    cameraOrtho.name = 'OrthographicCamera';
+
+    material = new THREE.MeshLambertMaterial({
+        color: 0xffff00,
+        side: THREE.DoubleSide,
+    });
+
+    object = new THREE.Mesh(new THREE.CircleGeometry(50, 20, 0, Math.PI * 2), material);
+    object.position.set(200, 0, -400);
+    scene1.add(object);
+
+    object = new THREE.Mesh(new THREE.RingGeometry(10, 50, 20, 5, 0, Math.PI * 2), material);
+    object.position.set(0, 0, -400);
+    scene1.add(object);
+
+    object = new THREE.Mesh(new THREE.CylinderGeometry(25, 75, 100, 40, 5), material);
+    object.position.set(-200, 0, -400);
+    scene1.add(object);
+
+    //
+    const points = [];
+
+    for (let i = 0; i < 50; i++) {
+        points.push(new THREE.Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
+    }
+
+    object = new THREE.Mesh(new THREE.LatheGeometry(points, 20), material);
+    object.position.set(200, 0, 400);
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // Big red box hidden just for testing `onlyVisible` option
+    // ---------------------------------------------------------------------
+    material = new THREE.MeshBasicMaterial({
+        color: 0xff0000,
+    });
+    object = new THREE.Mesh(new THREE.BoxGeometry(200, 200, 200), material);
+    object.position.set(0, 0, 0);
+    object.name = 'CubeHidden';
+    object.visible = false;
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // Model requiring KHR_mesh_quantization
+    // ---------------------------------------------------------------------
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/ShaderBall.glb', function (gltf) {
+        model = gltf.scene;
+        model.scale.setScalar(50);
+        model.position.set(200, -40, -200);
+        scene1.add(model);
+    });
+
+    // ---------------------------------------------------------------------
+    // Model requiring KHR_mesh_quantization
+    // ---------------------------------------------------------------------
+
+    material = new THREE.MeshBasicMaterial({
+        color: 0xffffff,
+    });
+    object = new THREE.InstancedMesh(new THREE.BoxGeometry(10, 10, 10, 2, 2, 2), material, 50);
+    const matrix = new THREE.Matrix4();
+    const color = new THREE.Color();
+    for (let i = 0; i < 50; i++) {
+        matrix.setPosition(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
+        object.setMatrixAt(i, matrix);
+        object.setColorAt(i, color.setHSL(i / 50, 1, 0.5));
+    }
+
+    object.position.set(400, 0, 200);
+    scene1.add(object);
+
+    // ---------------------------------------------------------------------
+    // 2nd THREE.Scene
+    // ---------------------------------------------------------------------
+    scene2 = new THREE.Scene();
+    object = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100), material);
+    object.position.set(0, 0, 0);
+    object.name = 'Cube2ndScene';
+    scene2.name = 'Scene2';
+    scene2.add(object);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    // ---------------------------------------------------------------------
+    // Exporting compressed textures and meshes (KTX2 / Draco / Meshopt)
+    // ---------------------------------------------------------------------
+    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
+
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
+    gltfLoader.setKTX2Loader(ktx2Loader);
+    gltfLoader.setMeshoptDecoder(MeshoptDecoder);
+    gltfLoader.load('coffeemat.glb', function (gltf) {
+        gltf.scene.position.x = 400;
+        gltf.scene.position.z = -200;
+
+        scene1.add(gltf.scene);
+
+        coffeemat = gltf.scene;
+    });
+
+    //
+
+    const gui = new GUI();
+
+    let h = gui.addFolder('Settings');
+    h.add(params, 'trs').name('Use TRS');
+    h.add(params, 'onlyVisible').name('Only Visible Objects');
+    h.add(params, 'binary').name('Binary (GLB)');
+    h.add(params, 'maxTextureSize', 2, 8192).name('Max Texture Size').step(1);
+
+    h = gui.addFolder('Export');
+    h.add(params, 'exportScene1').name('Export Scene 1');
+    h.add(params, 'exportScenes').name('Export Scene 1 and 2');
+    h.add(params, 'exportSphere').name('Export Sphere');
+    h.add(params, 'exportModel').name('Export Model');
+    h.add(params, 'exportObjects').name('Export Sphere With Grid');
+    h.add(params, 'exportSceneObject').name('Export Scene 1 and Object');
+    h.add(params, 'exportCompressedObject').name('Export Coffeemat (from compressed data)');
+
+    gui.open();
+}
+
+function exportScene1() {
+    exportGLTF(scene1);
+}
+
+function exportScenes() {
+    exportGLTF([scene1, scene2]);
+}
+
+function exportSphere() {
+    exportGLTF(sphere);
+}
+
+function exportModel() {
+    exportGLTF(model);
+}
+
+function exportObjects() {
+    exportGLTF([sphere, gridHelper]);
+}
+
+function exportSceneObject() {
+    exportGLTF([scene1, gridHelper]);
+}
+
+function exportCompressedObject() {
+    exportGLTF([coffeemat]);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const timer = Date.now() * 0.0001;
+
+    camera.position.x = Math.cos(timer) * 800;
+    camera.position.z = Math.sin(timer) * 800;
+
+    camera.lookAt(scene1.position);
+    renderer.render(scene1, camera);
+}
diff --git a/examples-testing/examples/misc_exporter_obj.ts b/examples-testing/examples/misc_exporter_obj.ts
new file mode 100644
index 000000000..025034daf
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_obj.ts
@@ -0,0 +1,192 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJExporter } from 'three/addons/exporters/OBJExporter.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+const params = {
+    addTriangle: addTriangle,
+    addCube: addCube,
+    addCylinder: addCylinder,
+    addMultiple: addMultiple,
+    addTransformed: addTransformed,
+    addPoints: addPoints,
+    exportToObj: exportToObj,
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 400);
+
+    scene = new THREE.Scene();
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
+    directionalLight.position.set(0, 1, 1);
+    scene.add(directionalLight);
+
+    const gui = new GUI();
+
+    let h = gui.addFolder('Geometry Selection');
+    h.add(params, 'addTriangle').name('Triangle');
+    h.add(params, 'addCube').name('Cube');
+    h.add(params, 'addCylinder').name('Cylinder');
+    h.add(params, 'addMultiple').name('Multiple objects');
+    h.add(params, 'addTransformed').name('Transformed objects');
+    h.add(params, 'addPoints').name('Point Cloud');
+
+    h = gui.addFolder('Export');
+    h.add(params, 'exportToObj').name('Export OBJ');
+
+    gui.open();
+
+    addGeometry(1);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enablePan = false;
+}
+
+function exportToObj() {
+    const exporter = new OBJExporter();
+    const result = exporter.parse(scene);
+    saveString(result, 'object.obj');
+}
+
+function addGeometry(type) {
+    for (let i = 0; i < scene.children.length; i++) {
+        const child = scene.children[i];
+
+        if (child.isMesh || child.isPoints) {
+            child.geometry.dispose();
+            scene.remove(child);
+            i--;
+        }
+    }
+
+    if (type === 1) {
+        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
+        const geometry = generateTriangleGeometry();
+
+        scene.add(new THREE.Mesh(geometry, material));
+    } else if (type === 2) {
+        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
+        const geometry = new THREE.BoxGeometry(100, 100, 100);
+        scene.add(new THREE.Mesh(geometry, material));
+    } else if (type === 3) {
+        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
+        const geometry = new THREE.CylinderGeometry(50, 50, 100, 30, 1);
+        scene.add(new THREE.Mesh(geometry, material));
+    } else if (type === 4 || type === 5) {
+        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
+        const geometry = generateTriangleGeometry();
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = -200;
+        scene.add(mesh);
+
+        const geometry2 = new THREE.BoxGeometry(100, 100, 100);
+        const mesh2 = new THREE.Mesh(geometry2, material);
+        scene.add(mesh2);
+
+        const geometry3 = new THREE.CylinderGeometry(50, 50, 100, 30, 1);
+        const mesh3 = new THREE.Mesh(geometry3, material);
+        mesh3.position.x = 200;
+        scene.add(mesh3);
+
+        if (type === 5) {
+            mesh.rotation.y = Math.PI / 4.0;
+            mesh2.rotation.y = Math.PI / 4.0;
+            mesh3.rotation.y = Math.PI / 4.0;
+        }
+    } else if (type === 6) {
+        const points = [0, 0, 0, 100, 0, 0, 100, 100, 0, 0, 100, 0];
+        const colors = [0.5, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0.5, 0];
+
+        const geometry = new THREE.BufferGeometry();
+        geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
+        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+        const material = new THREE.PointsMaterial({ size: 10, vertexColors: true });
+
+        const pointCloud = new THREE.Points(geometry, material);
+        pointCloud.name = 'point cloud';
+        scene.add(pointCloud);
+    }
+}
+
+function addTriangle() {
+    addGeometry(1);
+}
+
+function addCube() {
+    addGeometry(2);
+}
+
+function addCylinder() {
+    addGeometry(3);
+}
+
+function addMultiple() {
+    addGeometry(4);
+}
+
+function addTransformed() {
+    addGeometry(5);
+}
+
+function addPoints() {
+    addGeometry(6);
+}
+
+const link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link);
+
+function save(blob, filename) {
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+}
+
+function saveString(text, filename) {
+    save(new Blob([text], { type: 'text/plain' }), filename);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
+
+function generateTriangleGeometry() {
+    const geometry = new THREE.BufferGeometry();
+    const vertices = [];
+
+    vertices.push(-50, -50, 0);
+    vertices.push(50, -50, 0);
+    vertices.push(50, 50, 0);
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry.computeVertexNormals();
+
+    return geometry;
+}
diff --git a/examples-testing/examples/misc_exporter_ply.ts b/examples-testing/examples/misc_exporter_ply.ts
new file mode 100644
index 000000000..b7e324688
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_ply.ts
@@ -0,0 +1,156 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { PLYExporter } from 'three/addons/exporters/PLYExporter.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, renderer, exporter, mesh;
+
+const params = {
+    exportASCII: exportASCII,
+    exportBinaryBigEndian: exportBinaryBigEndian,
+    exportBinaryLittleEndian: exportBinaryLittleEndian,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(4, 2, 4);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
+
+    exporter = new PLYExporter();
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(0, 20, 10);
+    directionalLight.castShadow = true;
+    directionalLight.shadow.camera.top = 2;
+    directionalLight.shadow.camera.bottom = -2;
+    directionalLight.shadow.camera.left = -2;
+    directionalLight.shadow.camera.right = 2;
+    scene.add(directionalLight);
+
+    // ground
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(40, 40),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
+    );
+    ground.rotation.x = -Math.PI / 2;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
+    grid.material.opacity = 0.2;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    // export mesh
+
+    const geometry = new THREE.BoxGeometry();
+    const material = new THREE.MeshPhongMaterial({ vertexColors: true });
+
+    // color vertices based on vertex positions
+    const colors = geometry.getAttribute('position').array.slice();
+    for (let i = 0, l = colors.length; i < l; i++) {
+        if (colors[i] > 0) colors[i] = 0.5;
+        else colors[i] = 0;
+    }
+
+    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, false));
+
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.castShadow = true;
+    mesh.position.y = 0.5;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0.5, 0);
+    controls.update();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'exportASCII').name('Export PLY (ASCII)');
+    gui.add(params, 'exportBinaryBigEndian').name('Export PLY (Binary BE)');
+    gui.add(params, 'exportBinaryLittleEndian').name('Export PLY (Binary LE)');
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
+
+function exportASCII() {
+    exporter.parse(mesh, function (result) {
+        saveString(result, 'box.ply');
+    });
+}
+
+function exportBinaryBigEndian() {
+    exporter.parse(
+        mesh,
+        function (result) {
+            saveArrayBuffer(result, 'box.ply');
+        },
+        { binary: true },
+    );
+}
+
+function exportBinaryLittleEndian() {
+    exporter.parse(
+        mesh,
+        function (result) {
+            saveArrayBuffer(result, 'box.ply');
+        },
+        { binary: true, littleEndian: true },
+    );
+}
+
+const link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link);
+
+function save(blob, filename) {
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+}
+
+function saveString(text, filename) {
+    save(new Blob([text], { type: 'text/plain' }), filename);
+}
+
+function saveArrayBuffer(buffer, filename) {
+    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
+}
diff --git a/examples-testing/examples/misc_exporter_stl.ts b/examples-testing/examples/misc_exporter_stl.ts
new file mode 100644
index 000000000..ff6d6e2b5
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_stl.ts
@@ -0,0 +1,129 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { STLExporter } from 'three/addons/exporters/STLExporter.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, renderer, exporter, mesh;
+
+const params = {
+    exportASCII: exportASCII,
+    exportBinary: exportBinary,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(4, 2, 4);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
+
+    exporter = new STLExporter();
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(0, 20, 10);
+    directionalLight.castShadow = true;
+    directionalLight.shadow.camera.top = 2;
+    directionalLight.shadow.camera.bottom = -2;
+    directionalLight.shadow.camera.left = -2;
+    directionalLight.shadow.camera.right = 2;
+    scene.add(directionalLight);
+
+    // ground
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(40, 40),
+        new THREE.MeshPhongMaterial({ color: 0xbbbbbb, depthWrite: false }),
+    );
+    ground.rotation.x = -Math.PI / 2;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
+    grid.material.opacity = 0.2;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    // export mesh
+
+    const geometry = new THREE.BoxGeometry();
+    const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
+
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.castShadow = true;
+    mesh.position.y = 0.5;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0.5, 0);
+    controls.update();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'exportASCII').name('Export STL (ASCII)');
+    gui.add(params, 'exportBinary').name('Export STL (Binary)');
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
+
+function exportASCII() {
+    const result = exporter.parse(mesh);
+    saveString(result, 'box.stl');
+}
+
+function exportBinary() {
+    const result = exporter.parse(mesh, { binary: true });
+    saveArrayBuffer(result, 'box.stl');
+}
+
+const link = document.createElement('a');
+link.style.display = 'none';
+document.body.appendChild(link);
+
+function save(blob, filename) {
+    link.href = URL.createObjectURL(blob);
+    link.download = filename;
+    link.click();
+}
+
+function saveString(text, filename) {
+    save(new Blob([text], { type: 'text/plain' }), filename);
+}
+
+function saveArrayBuffer(buffer, filename) {
+    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
+}
diff --git a/examples-testing/examples/misc_exporter_usdz.ts b/examples-testing/examples/misc_exporter_usdz.ts
new file mode 100644
index 000000000..9a14919ba
--- /dev/null
+++ b/examples-testing/examples/misc_exporter_usdz.ts
@@ -0,0 +1,129 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { USDZExporter } from 'three/addons/exporters/USDZExporter.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+const params = {
+    exportUSDZ: exportUSDZ,
+};
+
+init();
+render();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-2.5, 0.6, 3.0);
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+    scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
+
+    const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+    loader.load('DamagedHelmet.gltf', async function (gltf) {
+        scene.add(gltf.scene);
+
+        const shadowMesh = createSpotShadowMesh();
+        shadowMesh.position.y = -1.1;
+        shadowMesh.position.z = -0.25;
+        shadowMesh.scale.setScalar(2);
+        scene.add(shadowMesh);
+
+        render();
+
+        // USDZ
+
+        const exporter = new USDZExporter();
+        const arraybuffer = await exporter.parseAsync(gltf.scene);
+        const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
+
+        const link = document.getElementById('link');
+        link.href = URL.createObjectURL(blob);
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, -0.15, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+
+    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
+
+    if (isIOS === false) {
+        const gui = new GUI();
+
+        gui.add(params, 'exportUSDZ').name('Export USDZ');
+        gui.open();
+    }
+}
+
+function createSpotShadowMesh() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 128;
+    canvas.height = 128;
+
+    const context = canvas.getContext('2d');
+    const gradient = context.createRadialGradient(
+        canvas.width / 2,
+        canvas.height / 2,
+        0,
+        canvas.width / 2,
+        canvas.height / 2,
+        canvas.width / 2,
+    );
+    gradient.addColorStop(0.1, 'rgba(130,130,130,1)');
+    gradient.addColorStop(1, 'rgba(255,255,255,1)');
+
+    context.fillStyle = gradient;
+    context.fillRect(0, 0, canvas.width, canvas.height);
+
+    const shadowTexture = new THREE.CanvasTexture(canvas);
+
+    const geometry = new THREE.PlaneGeometry();
+    const material = new THREE.MeshBasicMaterial({
+        map: shadowTexture,
+        blending: THREE.MultiplyBlending,
+        toneMapped: false,
+    });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    mesh.rotation.x = -Math.PI / 2;
+
+    return mesh;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function exportUSDZ() {
+    const link = document.getElementById('link');
+    link.click();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_lookat.ts b/examples-testing/examples/misc_lookat.ts
new file mode 100644
index 000000000..280b6e2d8
--- /dev/null
+++ b/examples-testing/examples/misc_lookat.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+
+let sphere;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+document.addEventListener('mousemove', onDocumentMouseMove);
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 15000);
+    camera.position.z = 3200;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    sphere = new THREE.Mesh(new THREE.SphereGeometry(100, 20, 20), new THREE.MeshNormalMaterial());
+    scene.add(sphere);
+
+    const geometry = new THREE.CylinderGeometry(0, 10, 100, 12);
+    geometry.rotateX(Math.PI / 2);
+
+    const material = new THREE.MeshNormalMaterial();
+
+    for (let i = 0; i < 1000; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 4000 - 2000;
+        mesh.position.y = Math.random() * 4000 - 2000;
+        mesh.position.z = Math.random() * 4000 - 2000;
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 4 + 2;
+        scene.add(mesh);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) * 10;
+    mouseY = (event.clientY - windowHalfY) * 10;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.0005;
+
+    sphere.position.x = Math.sin(time * 0.7) * 2000;
+    sphere.position.y = Math.cos(time * 0.5) * 2000;
+    sphere.position.z = Math.cos(time * 0.3) * 2000;
+
+    for (let i = 1, l = scene.children.length; i < l; i++) {
+        scene.children[i].lookAt(sphere.position);
+    }
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+    camera.lookAt(scene.position);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/misc_uv_tests.ts b/examples-testing/examples/misc_uv_tests.ts
new file mode 100644
index 000000000..4f782d45f
--- /dev/null
+++ b/examples-testing/examples/misc_uv_tests.ts
@@ -0,0 +1,44 @@
+import * as THREE from 'three';
+
+import { UVsDebug } from 'three/addons/utils/UVsDebug.js';
+
+/*
+ * This is to help debug UVs problems in geometry,
+ * as well as allow a new user to visualize what UVs are about.
+ */
+
+function test(name, geometry) {
+    const d = document.createElement('div');
+
+    d.innerHTML = '<h3>' + name + '</h3>';
+
+    d.appendChild(UVsDebug(geometry));
+
+    document.body.appendChild(d);
+}
+
+const points = [];
+
+for (let i = 0; i < 10; i++) {
+    points.push(new THREE.Vector2(Math.sin(i * 0.2) * 15 + 50, (i - 5) * 2));
+}
+
+//
+
+test('new THREE.PlaneGeometry( 100, 100, 4, 4 )', new THREE.PlaneGeometry(100, 100, 4, 4));
+
+test('new THREE.SphereGeometry( 75, 12, 6 )', new THREE.SphereGeometry(75, 12, 6));
+
+test('new THREE.IcosahedronGeometry( 30, 1 )', new THREE.IcosahedronGeometry(30, 1));
+
+test('new THREE.OctahedronGeometry( 30, 2 )', new THREE.OctahedronGeometry(30, 2));
+
+test('new THREE.CylinderGeometry( 25, 75, 100, 10, 5 )', new THREE.CylinderGeometry(25, 75, 100, 10, 5));
+
+test('new THREE.BoxGeometry( 100, 100, 100, 4, 4, 4 )', new THREE.BoxGeometry(100, 100, 100, 4, 4, 4));
+
+test('new THREE.LatheGeometry( points, 8 )', new THREE.LatheGeometry(points, 8));
+
+test('new THREE.TorusGeometry( 50, 20, 8, 8 )', new THREE.TorusGeometry(50, 20, 8, 8));
+
+test('new THREE.TorusKnotGeometry( 50, 10, 12, 6 )', new THREE.TorusKnotGeometry(50, 10, 12, 6));
diff --git a/examples-testing/examples/physics_ammo_instancing.ts b/examples-testing/examples/physics_ammo_instancing.ts
new file mode 100644
index 000000000..265c254c8
--- /dev/null
+++ b/examples-testing/examples/physics_ammo_instancing.ts
@@ -0,0 +1,119 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+let physics, position;
+
+let boxes, spheres;
+
+init();
+
+async function init() {
+    physics = await AmmoPhysics();
+    position = new THREE.Vector3();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(-1, 1.5, 2);
+    camera.lookAt(0, 0.5, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x666666);
+
+    const hemiLight = new THREE.HemisphereLight();
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(5, 5, 5);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.zoom = 2;
+    scene.add(dirLight);
+
+    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
+    floor.position.y = -2.5;
+    floor.receiveShadow = true;
+    floor.userData.physics = { mass: 0 };
+    scene.add(floor);
+
+    //
+
+    const material = new THREE.MeshLambertMaterial();
+
+    const matrix = new THREE.Matrix4();
+    const color = new THREE.Color();
+
+    // Boxes
+
+    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
+    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
+    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    boxes.castShadow = true;
+    boxes.receiveShadow = true;
+    boxes.userData.physics = { mass: 1 };
+    scene.add(boxes);
+
+    for (let i = 0; i < boxes.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        boxes.setMatrixAt(i, matrix);
+        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    // Spheres
+
+    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
+    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
+    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    spheres.castShadow = true;
+    spheres.receiveShadow = true;
+    spheres.userData.physics = { mass: 1 };
+    scene.add(spheres);
+
+    for (let i = 0; i < spheres.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        spheres.setMatrixAt(i, matrix);
+        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    physics.addScene(scene);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.y = 0.5;
+    controls.update();
+
+    setInterval(() => {
+        let index = Math.floor(Math.random() * boxes.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(boxes, position, index);
+
+        //
+
+        index = Math.floor(Math.random() * spheres.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(spheres, position, index);
+    }, 1000 / 60);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/physics_jolt_instancing.ts b/examples-testing/examples/physics_jolt_instancing.ts
new file mode 100644
index 000000000..022263c0d
--- /dev/null
+++ b/examples-testing/examples/physics_jolt_instancing.ts
@@ -0,0 +1,119 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { JoltPhysics } from 'three/addons/physics/JoltPhysics.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+let physics, position;
+
+let boxes, spheres;
+
+init();
+
+async function init() {
+    physics = await JoltPhysics();
+    position = new THREE.Vector3();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(-1, 1.5, 2);
+    camera.lookAt(0, 0.5, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x666666);
+
+    const hemiLight = new THREE.HemisphereLight();
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(5, 5, 5);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.zoom = 2;
+    scene.add(dirLight);
+
+    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
+    floor.position.y = -2.5;
+    floor.receiveShadow = true;
+    floor.userData.physics = { mass: 0 };
+    scene.add(floor);
+
+    //
+
+    const material = new THREE.MeshLambertMaterial();
+
+    const matrix = new THREE.Matrix4();
+    const color = new THREE.Color();
+
+    // Boxes
+
+    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
+    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
+    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    boxes.castShadow = true;
+    boxes.receiveShadow = true;
+    boxes.userData.physics = { mass: 1 };
+    scene.add(boxes);
+
+    for (let i = 0; i < boxes.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        boxes.setMatrixAt(i, matrix);
+        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    // Spheres
+
+    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
+    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
+    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    spheres.castShadow = true;
+    spheres.receiveShadow = true;
+    spheres.userData.physics = { mass: 1 };
+    scene.add(spheres);
+
+    for (let i = 0; i < spheres.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        spheres.setMatrixAt(i, matrix);
+        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    physics.addScene(scene);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.y = 0.5;
+    controls.update();
+
+    setInterval(() => {
+        let index = Math.floor(Math.random() * boxes.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(boxes, position, index);
+
+        //
+
+        index = Math.floor(Math.random() * spheres.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(spheres, position, index);
+    }, 1000 / 60);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/physics_rapier_instancing.ts b/examples-testing/examples/physics_rapier_instancing.ts
new file mode 100644
index 000000000..f23cf7667
--- /dev/null
+++ b/examples-testing/examples/physics_rapier_instancing.ts
@@ -0,0 +1,119 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+let physics, position;
+
+let boxes, spheres;
+
+init();
+
+async function init() {
+    physics = await RapierPhysics();
+    position = new THREE.Vector3();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(-1, 1.5, 2);
+    camera.lookAt(0, 0.5, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x666666);
+
+    const hemiLight = new THREE.HemisphereLight();
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(5, 5, 5);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.zoom = 2;
+    scene.add(dirLight);
+
+    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
+    floor.position.y = -2.5;
+    floor.receiveShadow = true;
+    floor.userData.physics = { mass: 0 };
+    scene.add(floor);
+
+    //
+
+    const material = new THREE.MeshLambertMaterial();
+
+    const matrix = new THREE.Matrix4();
+    const color = new THREE.Color();
+
+    // Boxes
+
+    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
+    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
+    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    boxes.castShadow = true;
+    boxes.receiveShadow = true;
+    boxes.userData.physics = { mass: 1 };
+    scene.add(boxes);
+
+    for (let i = 0; i < boxes.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        boxes.setMatrixAt(i, matrix);
+        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    // Spheres
+
+    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
+    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
+    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+    spheres.castShadow = true;
+    spheres.receiveShadow = true;
+    spheres.userData.physics = { mass: 1 };
+    scene.add(spheres);
+
+    for (let i = 0; i < spheres.count; i++) {
+        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
+        spheres.setMatrixAt(i, matrix);
+        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
+    }
+
+    physics.addScene(scene);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.y = 0.5;
+    controls.update();
+
+    setInterval(() => {
+        let index = Math.floor(Math.random() * boxes.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(boxes, position, index);
+
+        //
+
+        index = Math.floor(Math.random() * spheres.count);
+
+        position.set(0, Math.random() + 1, 0);
+        physics.setMeshPosition(spheres, position, index);
+    }, 1000 / 60);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/svg_lines.ts b/examples-testing/examples/svg_lines.ts
new file mode 100644
index 000000000..99b74c405
--- /dev/null
+++ b/examples-testing/examples/svg_lines.ts
@@ -0,0 +1,87 @@
+import * as THREE from 'three';
+
+import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js';
+
+THREE.ColorManagement.enabled = false;
+
+let camera, scene, renderer;
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(33, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 10;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0, 0, 0);
+
+    renderer = new SVGRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const vertices = [];
+    const divisions = 50;
+
+    for (let i = 0; i <= divisions; i++) {
+        const v = (i / divisions) * (Math.PI * 2);
+
+        const x = Math.sin(v);
+        const z = Math.cos(v);
+
+        vertices.push(x, 0, z);
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    //
+
+    for (let i = 1; i <= 3; i++) {
+        const material = new THREE.LineBasicMaterial({
+            color: Math.random() * 0xffffff,
+            linewidth: 10,
+        });
+        const line = new THREE.Line(geometry, material);
+        line.scale.setScalar(i / 3);
+        scene.add(line);
+    }
+
+    const material = new THREE.LineDashedMaterial({
+        color: 'blue',
+        linewidth: 1,
+        dashSize: 10,
+        gapSize: 10,
+    });
+    const line = new THREE.Line(geometry, material);
+    line.scale.setScalar(2);
+    scene.add(line);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    let count = 0;
+    const time = performance.now() / 1000;
+
+    scene.traverse(function (child) {
+        child.rotation.x = count + time / 3;
+        child.rotation.z = count + time / 4;
+
+        count++;
+    });
+
+    renderer.render(scene, camera);
+    requestAnimationFrame(animate);
+}
diff --git a/examples-testing/examples/svg_sandbox.ts b/examples-testing/examples/svg_sandbox.ts
new file mode 100644
index 000000000..e6be8386c
--- /dev/null
+++ b/examples-testing/examples/svg_sandbox.ts
@@ -0,0 +1,212 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { SVGRenderer, SVGObject } from 'three/addons/renderers/SVGRenderer.js';
+
+THREE.ColorManagement.enabled = false;
+
+let camera, scene, renderer, stats;
+
+let group;
+
+init();
+animate();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    // QRCODE
+
+    const loader = new THREE.BufferGeometryLoader();
+    loader.load('models/json/QRCode_buffergeometry.json', function (geometry) {
+        mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ vertexColors: true }));
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
+        scene.add(mesh);
+    });
+
+    // CUBES
+
+    const boxGeometry = new THREE.BoxGeometry(100, 100, 100);
+
+    let mesh = new THREE.Mesh(
+        boxGeometry,
+        new THREE.MeshBasicMaterial({ color: 0x0000ff, opacity: 0.5, transparent: true }),
+    );
+    mesh.position.x = 500;
+    mesh.rotation.x = Math.random();
+    mesh.rotation.y = Math.random();
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff }));
+    mesh.position.x = 500;
+    mesh.position.y = 500;
+    mesh.rotation.x = Math.random();
+    mesh.rotation.y = Math.random();
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
+    scene.add(mesh);
+
+    // PLANE
+
+    mesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(100, 100),
+        new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, side: THREE.DoubleSide }),
+    );
+    mesh.position.y = -500;
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
+    scene.add(mesh);
+
+    // CYLINDER
+
+    mesh = new THREE.Mesh(
+        new THREE.CylinderGeometry(20, 100, 200, 10),
+        new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff }),
+    );
+    mesh.position.x = -500;
+    mesh.rotation.x = -Math.PI / 2;
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
+    scene.add(mesh);
+
+    // POLYFIELD
+
+    const geometry = new THREE.BufferGeometry();
+    const material = new THREE.MeshBasicMaterial({ vertexColors: true, side: THREE.DoubleSide });
+
+    const v = new THREE.Vector3();
+    const v0 = new THREE.Vector3();
+    const v1 = new THREE.Vector3();
+    const v2 = new THREE.Vector3();
+    const color = new THREE.Color();
+
+    const vertices = [];
+    const colors = [];
+
+    for (let i = 0; i < 100; i++) {
+        v.set(Math.random() * 1000 - 500, Math.random() * 1000 - 500, Math.random() * 1000 - 500);
+
+        v0.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
+
+        v1.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
+
+        v2.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
+
+        v0.add(v);
+        v1.add(v);
+        v2.add(v);
+
+        color.setHex(Math.random() * 0xffffff);
+
+        // create a single triangle
+
+        vertices.push(v0.x, v0.y, v0.z);
+        vertices.push(v1.x, v1.y, v1.z);
+        vertices.push(v2.x, v2.y, v2.z);
+
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    group = new THREE.Mesh(geometry, material);
+    group.scale.set(2, 2, 2);
+    scene.add(group);
+
+    // SPRITES
+
+    for (let i = 0; i < 50; i++) {
+        const material = new THREE.SpriteMaterial({ color: Math.random() * 0xffffff });
+        const sprite = new THREE.Sprite(material);
+        sprite.position.x = Math.random() * 1000 - 500;
+        sprite.position.y = Math.random() * 1000 - 500;
+        sprite.position.z = Math.random() * 1000 - 500;
+        sprite.scale.set(64, 64, 1);
+        scene.add(sprite);
+    }
+
+    // CUSTOM
+
+    const node = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
+    node.setAttribute('stroke', 'black');
+    node.setAttribute('fill', 'red');
+    node.setAttribute('r', '40');
+
+    for (let i = 0; i < 50; i++) {
+        const object = new SVGObject(node.cloneNode());
+        object.position.x = Math.random() * 1000 - 500;
+        object.position.y = Math.random() * 1000 - 500;
+        object.position.z = Math.random() * 1000 - 500;
+        scene.add(object);
+    }
+
+    // CUSTOM FROM FILE
+
+    const fileLoader = new THREE.FileLoader();
+    fileLoader.load('models/svg/hexagon.svg', function (svg) {
+        const node = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+        const parser = new DOMParser();
+        const doc = parser.parseFromString(svg, 'image/svg+xml');
+
+        node.appendChild(doc.documentElement);
+
+        const object = new SVGObject(node);
+        object.position.x = 500;
+        scene.add(object);
+    });
+
+    // LIGHTS
+
+    const ambient = new THREE.AmbientLight(0x80ffff);
+    scene.add(ambient);
+
+    const directional = new THREE.DirectionalLight(0xffff00);
+    directional.position.set(-1, 0.5, 0);
+    scene.add(directional);
+
+    renderer = new SVGRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setQuality('low');
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.0002;
+
+    camera.position.x = Math.sin(time) * 500;
+    camera.position.z = Math.cos(time) * 500;
+    camera.lookAt(scene.position);
+
+    group.rotation.x += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webaudio_orientation.ts b/examples-testing/examples/webaudio_orientation.ts
new file mode 100644
index 000000000..7baaa88a0
--- /dev/null
+++ b/examples-testing/examples/webaudio_orientation.ts
@@ -0,0 +1,141 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { PositionalAudioHelper } from 'three/addons/helpers/PositionalAudioHelper.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let scene, camera, renderer;
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', init);
+
+function init() {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    const container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(3, 2, 3);
+
+    const reflectionCube = new THREE.CubeTextureLoader()
+        .setPath('textures/cube/SwedishRoyalCastle/')
+        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 2, 20);
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(5, 5, 0);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.top = 1;
+    dirLight.shadow.camera.bottom = -1;
+    dirLight.shadow.camera.left = -1;
+    dirLight.shadow.camera.right = 1;
+    dirLight.shadow.camera.near = 0.1;
+    dirLight.shadow.camera.far = 20;
+    scene.add(dirLight);
+
+    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
+
+    //
+
+    const mesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(50, 50),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
+    );
+    mesh.rotation.x = -Math.PI / 2;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    const grid = new THREE.GridHelper(50, 50, 0xc1c1c1, 0xc1c1c1);
+    scene.add(grid);
+
+    //
+
+    const listener = new THREE.AudioListener();
+    camera.add(listener);
+
+    const audioElement = document.getElementById('music');
+    audioElement.play();
+
+    const positionalAudio = new THREE.PositionalAudio(listener);
+    positionalAudio.setMediaElementSource(audioElement);
+    positionalAudio.setRefDistance(1);
+    positionalAudio.setDirectionalCone(180, 230, 0.1);
+
+    const helper = new PositionalAudioHelper(positionalAudio, 0.1);
+    positionalAudio.add(helper);
+
+    //
+
+    const gltfLoader = new GLTFLoader();
+    gltfLoader.load('models/gltf/BoomBox.glb', function (gltf) {
+        const boomBox = gltf.scene;
+        boomBox.position.set(0, 0.2, 0);
+        boomBox.scale.set(20, 20, 20);
+
+        boomBox.traverse(function (object) {
+            if (object.isMesh) {
+                object.material.envMap = reflectionCube;
+                object.geometry.rotateY(-Math.PI);
+                object.castShadow = true;
+            }
+        });
+
+        boomBox.add(positionalAudio);
+        scene.add(boomBox);
+
+        renderer.setAnimationLoop(animate);
+    });
+
+    // sound is damped behind this wall
+
+    const wallGeometry = new THREE.BoxGeometry(2, 1, 0.1);
+    const wallMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5 });
+
+    const wall = new THREE.Mesh(wallGeometry, wallMaterial);
+    wall.position.set(0, 0.5, -0.5);
+    scene.add(wall);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0.1, 0);
+    controls.update();
+    controls.minDistance = 0.5;
+    controls.maxDistance = 10;
+    controls.maxPolarAngle = 0.5 * Math.PI;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webaudio_sandbox.ts b/examples-testing/examples/webaudio_sandbox.ts
new file mode 100644
index 000000000..d67d0d552
--- /dev/null
+++ b/examples-testing/examples/webaudio_sandbox.ts
@@ -0,0 +1,222 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+
+let camera, controls, scene, renderer, light;
+
+let material1, material2, material3;
+
+let analyser1, analyser2, analyser3;
+
+const clock = new THREE.Clock();
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', init);
+
+function init() {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(0, 25, 0);
+
+    const listener = new THREE.AudioListener();
+    camera.add(listener);
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.FogExp2(0x000000, 0.0025);
+
+    light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 0.5, 1).normalize();
+    scene.add(light);
+
+    const sphere = new THREE.SphereGeometry(20, 32, 16);
+
+    material1 = new THREE.MeshPhongMaterial({ color: 0xffaa00, flatShading: true, shininess: 0 });
+    material2 = new THREE.MeshPhongMaterial({ color: 0xff2200, flatShading: true, shininess: 0 });
+    material3 = new THREE.MeshPhongMaterial({ color: 0x6622aa, flatShading: true, shininess: 0 });
+
+    // sound spheres
+
+    const mesh1 = new THREE.Mesh(sphere, material1);
+    mesh1.position.set(-250, 30, 0);
+    scene.add(mesh1);
+
+    const sound1 = new THREE.PositionalAudio(listener);
+    const songElement = document.getElementById('song');
+    sound1.setMediaElementSource(songElement);
+    sound1.setRefDistance(20);
+    songElement.play();
+    mesh1.add(sound1);
+
+    //
+
+    const mesh2 = new THREE.Mesh(sphere, material2);
+    mesh2.position.set(250, 30, 0);
+    scene.add(mesh2);
+
+    const sound2 = new THREE.PositionalAudio(listener);
+    const skullbeatzElement = document.getElementById('skullbeatz');
+    sound2.setMediaElementSource(skullbeatzElement);
+    sound2.setRefDistance(20);
+    skullbeatzElement.play();
+    mesh2.add(sound2);
+
+    //
+
+    const mesh3 = new THREE.Mesh(sphere, material3);
+    mesh3.position.set(0, 30, -250);
+    scene.add(mesh3);
+
+    const sound3 = new THREE.PositionalAudio(listener);
+    const oscillator = listener.context.createOscillator();
+    oscillator.type = 'sine';
+    oscillator.frequency.setValueAtTime(144, sound3.context.currentTime);
+    oscillator.start(0);
+    sound3.setNodeSource(oscillator);
+    sound3.setRefDistance(20);
+    sound3.setVolume(0.5);
+    mesh3.add(sound3);
+
+    // analysers
+
+    analyser1 = new THREE.AudioAnalyser(sound1, 32);
+    analyser2 = new THREE.AudioAnalyser(sound2, 32);
+    analyser3 = new THREE.AudioAnalyser(sound3, 32);
+
+    // global ambient audio
+
+    const sound4 = new THREE.Audio(listener);
+    const utopiaElement = document.getElementById('utopia');
+    sound4.setMediaElementSource(utopiaElement);
+    sound4.setVolume(0.5);
+    utopiaElement.play();
+
+    // ground
+
+    const helper = new THREE.GridHelper(1000, 10, 0x444444, 0x444444);
+    helper.position.y = 0.1;
+    scene.add(helper);
+
+    //
+
+    const SoundControls = function () {
+        this.master = listener.getMasterVolume();
+        this.firstSphere = sound1.getVolume();
+        this.secondSphere = sound2.getVolume();
+        this.thirdSphere = sound3.getVolume();
+        this.Ambient = sound4.getVolume();
+    };
+
+    const GeneratorControls = function () {
+        this.frequency = oscillator.frequency.value;
+        this.wavetype = oscillator.type;
+    };
+
+    const gui = new GUI();
+    const soundControls = new SoundControls();
+    const generatorControls = new GeneratorControls();
+    const volumeFolder = gui.addFolder('sound volume');
+    const generatorFolder = gui.addFolder('sound generator');
+
+    volumeFolder
+        .add(soundControls, 'master')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(function () {
+            listener.setMasterVolume(soundControls.master);
+        });
+    volumeFolder
+        .add(soundControls, 'firstSphere')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(function () {
+            sound1.setVolume(soundControls.firstSphere);
+        });
+    volumeFolder
+        .add(soundControls, 'secondSphere')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(function () {
+            sound2.setVolume(soundControls.secondSphere);
+        });
+
+    volumeFolder
+        .add(soundControls, 'thirdSphere')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(function () {
+            sound3.setVolume(soundControls.thirdSphere);
+        });
+    volumeFolder
+        .add(soundControls, 'Ambient')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(function () {
+            sound4.setVolume(soundControls.Ambient);
+        });
+    volumeFolder.open();
+    generatorFolder
+        .add(generatorControls, 'frequency')
+        .min(50.0)
+        .max(5000.0)
+        .step(1.0)
+        .onChange(function () {
+            oscillator.frequency.setValueAtTime(generatorControls.frequency, listener.context.currentTime);
+        });
+    generatorFolder
+        .add(generatorControls, 'wavetype', ['sine', 'square', 'sawtooth', 'triangle'])
+        .onChange(function () {
+            oscillator.type = generatorControls.wavetype;
+        });
+
+    generatorFolder.open();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+
+    controls.movementSpeed = 70;
+    controls.lookSpeed = 0.05;
+    controls.lookVertical = false;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    controls.update(delta);
+
+    material1.emissive.b = analyser1.getAverageFrequency() / 256;
+    material2.emissive.b = analyser2.getAverageFrequency() / 256;
+    material3.emissive.b = analyser3.getAverageFrequency() / 256;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webaudio_timing.ts b/examples-testing/examples/webaudio_timing.ts
new file mode 100644
index 000000000..9e17bcbcd
--- /dev/null
+++ b/examples-testing/examples/webaudio_timing.ts
@@ -0,0 +1,152 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let scene, camera, renderer, clock;
+
+const objects = [];
+
+const speed = 2.5;
+const height = 3;
+const offset = 0.5;
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', init);
+
+function init() {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    const container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(7, 3, 7);
+
+    // lights
+
+    const ambientLight = new THREE.AmbientLight(0xcccccc);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
+    directionalLight.position.set(0, 5, 5);
+    scene.add(directionalLight);
+
+    const d = 5;
+    directionalLight.castShadow = true;
+    directionalLight.shadow.camera.left = -d;
+    directionalLight.shadow.camera.right = d;
+    directionalLight.shadow.camera.top = d;
+    directionalLight.shadow.camera.bottom = -d;
+
+    directionalLight.shadow.camera.near = 1;
+    directionalLight.shadow.camera.far = 20;
+
+    directionalLight.shadow.mapSize.x = 1024;
+    directionalLight.shadow.mapSize.y = 1024;
+
+    // audio
+
+    const audioLoader = new THREE.AudioLoader();
+
+    const listener = new THREE.AudioListener();
+    camera.add(listener);
+
+    // floor
+
+    const floorGeometry = new THREE.PlaneGeometry(10, 10);
+    const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x4676b6 });
+
+    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
+    floor.rotation.x = Math.PI * -0.5;
+    floor.receiveShadow = true;
+    scene.add(floor);
+
+    // objects
+
+    const count = 5;
+    const radius = 3;
+
+    const ballGeometry = new THREE.SphereGeometry(0.3, 32, 16);
+    ballGeometry.translate(0, 0.3, 0);
+    const ballMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
+
+    // create objects when audio buffer is loaded
+
+    audioLoader.load('sounds/ping_pong.mp3', function (buffer) {
+        for (let i = 0; i < count; i++) {
+            const s = (i / count) * Math.PI * 2;
+
+            const ball = new THREE.Mesh(ballGeometry, ballMaterial);
+            ball.castShadow = true;
+            ball.userData.down = false;
+
+            ball.position.x = radius * Math.cos(s);
+            ball.position.z = radius * Math.sin(s);
+
+            const audio = new THREE.PositionalAudio(listener);
+            audio.setBuffer(buffer);
+            ball.add(audio);
+
+            scene.add(ball);
+            objects.push(ball);
+        }
+
+        renderer.setAnimationLoop(animate);
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 25;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = clock.getElapsedTime();
+
+    for (let i = 0; i < objects.length; i++) {
+        const ball = objects[i];
+
+        const previousHeight = ball.position.y;
+        ball.position.y = Math.abs(Math.sin(i * offset + time * speed) * height);
+
+        if (ball.position.y < previousHeight) {
+            ball.userData.down = true;
+        } else {
+            if (ball.userData.down === true) {
+                // ball changed direction from down to up
+
+                const audio = ball.children[0];
+                audio.play(); // play audio with perfect timing when ball hits the surface
+                ball.userData.down = false;
+            }
+        }
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webaudio_visualizer.ts b/examples-testing/examples/webaudio_visualizer.ts
new file mode 100644
index 000000000..a3f58cb36
--- /dev/null
+++ b/examples-testing/examples/webaudio_visualizer.ts
@@ -0,0 +1,86 @@
+import * as THREE from 'three';
+
+let scene, camera, renderer, analyser, uniforms;
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', init);
+
+function init() {
+    const fftSize = 128;
+
+    //
+
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    //
+
+    const container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.Camera();
+
+    //
+
+    const listener = new THREE.AudioListener();
+
+    const audio = new THREE.Audio(listener);
+    const file = './sounds/376737_Skullbeatz___Bad_Cat_Maste.mp3';
+
+    if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
+        const loader = new THREE.AudioLoader();
+        loader.load(file, function (buffer) {
+            audio.setBuffer(buffer);
+            audio.play();
+        });
+    } else {
+        const mediaElement = new Audio(file);
+        mediaElement.play();
+
+        audio.setMediaElementSource(mediaElement);
+    }
+
+    analyser = new THREE.AudioAnalyser(audio, fftSize);
+
+    //
+
+    uniforms = {
+        tAudioData: { value: new THREE.DataTexture(analyser.data, fftSize / 2, 1, THREE.RedFormat) },
+    };
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+    });
+
+    const geometry = new THREE.PlaneGeometry(1, 1);
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    analyser.getFrequencyData();
+
+    uniforms.tAudioData.value.needsUpdate = true;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_animation_keyframes.ts b/examples-testing/examples/webgl_animation_keyframes.ts
new file mode 100644
index 000000000..88048f24c
--- /dev/null
+++ b/examples-testing/examples/webgl_animation_keyframes.ts
@@ -0,0 +1,80 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+let mixer;
+
+const clock = new THREE.Clock();
+const container = document.getElementById('container');
+
+const stats = new Stats();
+container.appendChild(stats.dom);
+
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+container.appendChild(renderer.domElement);
+
+const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+const scene = new THREE.Scene();
+scene.background = new THREE.Color(0xbfe3dd);
+scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
+
+const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
+camera.position.set(5, 2, 8);
+
+const controls = new OrbitControls(camera, renderer.domElement);
+controls.target.set(0, 0.5, 0);
+controls.update();
+controls.enablePan = false;
+controls.enableDamping = true;
+
+const dracoLoader = new DRACOLoader();
+dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
+
+const loader = new GLTFLoader();
+loader.setDRACOLoader(dracoLoader);
+loader.load(
+    'models/gltf/LittlestTokyo.glb',
+    function (gltf) {
+        const model = gltf.scene;
+        model.position.set(1, 1, 0);
+        model.scale.set(0.01, 0.01, 0.01);
+        scene.add(model);
+
+        mixer = new THREE.AnimationMixer(model);
+        mixer.clipAction(gltf.animations[0]).play();
+
+        renderer.setAnimationLoop(animate);
+    },
+    undefined,
+    function (e) {
+        console.error(e);
+    },
+);
+
+window.onresize = function () {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+};
+
+function animate() {
+    const delta = clock.getDelta();
+
+    mixer.update(delta);
+
+    controls.update();
+
+    stats.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_animation_multiple.ts b/examples-testing/examples/webgl_animation_multiple.ts
new file mode 100644
index 000000000..152c65067
--- /dev/null
+++ b/examples-testing/examples/webgl_animation_multiple.ts
@@ -0,0 +1,197 @@
+import * as THREE from 'three';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, clock;
+let model, animations;
+
+const mixers = [],
+    objects = [];
+
+const params = {
+    sharedSkeleton: false,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(2, 3, -6);
+    camera.lookAt(0, 1, 0);
+
+    clock = new THREE.Clock();
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(-3, 10, -10);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.top = 4;
+    dirLight.shadow.camera.bottom = -4;
+    dirLight.shadow.camera.left = -4;
+    dirLight.shadow.camera.right = 4;
+    dirLight.shadow.camera.near = 0.1;
+    dirLight.shadow.camera.far = 40;
+    scene.add(dirLight);
+
+    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
+
+    // ground
+
+    const mesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(200, 200),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
+    );
+    mesh.rotation.x = -Math.PI / 2;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/Soldier.glb', function (gltf) {
+        model = gltf.scene;
+        animations = gltf.animations;
+
+        model.traverse(function (object) {
+            if (object.isMesh) object.castShadow = true;
+        });
+
+        setupDefaultScene();
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'sharedSkeleton').onChange(function () {
+        clearScene();
+
+        if (params.sharedSkeleton === true) {
+            setupSharedSkeletonScene();
+        } else {
+            setupDefaultScene();
+        }
+    });
+    gui.open();
+}
+
+function clearScene() {
+    for (const mixer of mixers) {
+        mixer.stopAllAction();
+    }
+
+    mixers.length = 0;
+
+    //
+
+    for (const object of objects) {
+        scene.remove(object);
+
+        scene.traverse(function (child) {
+            if (child.isSkinnedMesh) child.skeleton.dispose();
+        });
+    }
+}
+
+function setupDefaultScene() {
+    // three cloned models with independent skeletons.
+    // each model can have its own animation state
+
+    const model1 = SkeletonUtils.clone(model);
+    const model2 = SkeletonUtils.clone(model);
+    const model3 = SkeletonUtils.clone(model);
+
+    model1.position.x = -2;
+    model2.position.x = 0;
+    model3.position.x = 2;
+
+    const mixer1 = new THREE.AnimationMixer(model1);
+    const mixer2 = new THREE.AnimationMixer(model2);
+    const mixer3 = new THREE.AnimationMixer(model3);
+
+    mixer1.clipAction(animations[0]).play(); // idle
+    mixer2.clipAction(animations[1]).play(); // run
+    mixer3.clipAction(animations[3]).play(); // walk
+
+    scene.add(model1, model2, model3);
+
+    objects.push(model1, model2, model3);
+    mixers.push(mixer1, mixer2, mixer3);
+}
+
+function setupSharedSkeletonScene() {
+    // three cloned models with a single shared skeleton.
+    // all models share the same animation state
+
+    const sharedModel = SkeletonUtils.clone(model);
+    const shareSkinnedMesh = sharedModel.getObjectByName('vanguard_Mesh');
+    const sharedSkeleton = shareSkinnedMesh.skeleton;
+    const sharedParentBone = sharedModel.getObjectByName('mixamorigHips');
+    scene.add(sharedParentBone); // the bones need to be in the scene for the animation to work
+
+    const model1 = shareSkinnedMesh.clone();
+    const model2 = shareSkinnedMesh.clone();
+    const model3 = shareSkinnedMesh.clone();
+
+    model1.bindMode = THREE.DetachedBindMode;
+    model2.bindMode = THREE.DetachedBindMode;
+    model3.bindMode = THREE.DetachedBindMode;
+
+    const identity = new THREE.Matrix4();
+
+    model1.bind(sharedSkeleton, identity);
+    model2.bind(sharedSkeleton, identity);
+    model3.bind(sharedSkeleton, identity);
+
+    model1.position.x = -2;
+    model2.position.x = 0;
+    model3.position.x = 2;
+
+    // apply transformation from the glTF asset
+
+    model1.scale.setScalar(0.01);
+    model1.rotation.x = -Math.PI * 0.5;
+    model2.scale.setScalar(0.01);
+    model2.rotation.x = -Math.PI * 0.5;
+    model3.scale.setScalar(0.01);
+    model3.rotation.x = -Math.PI * 0.5;
+
+    //
+
+    const mixer = new THREE.AnimationMixer(sharedParentBone);
+    mixer.clipAction(animations[1]).play();
+
+    scene.add(sharedParentBone, model1, model2, model3);
+
+    objects.push(sharedParentBone, model1, model2, model3);
+    mixers.push(mixer);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    for (const mixer of mixers) mixer.update(delta);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_animation_skinning_morph.ts b/examples-testing/examples/webgl_animation_skinning_morph.ts
new file mode 100644
index 000000000..f05369aa9
--- /dev/null
+++ b/examples-testing/examples/webgl_animation_skinning_morph.ts
@@ -0,0 +1,187 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let container, stats, clock, gui, mixer, actions, activeAction, previousAction;
+let camera, scene, renderer, model, face;
+
+const api = { state: 'Walking' };
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 100);
+    camera.position.set(-5, 3, 10);
+    camera.lookAt(0, 2, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xe0e0e0);
+    scene.fog = new THREE.Fog(0xe0e0e0, 20, 100);
+
+    clock = new THREE.Clock();
+
+    // lights
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
+    hemiLight.position.set(0, 20, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(0, 20, 10);
+    scene.add(dirLight);
+
+    // ground
+
+    const mesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(2000, 2000),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
+    );
+    mesh.rotation.x = -Math.PI / 2;
+    scene.add(mesh);
+
+    const grid = new THREE.GridHelper(200, 40, 0x000000, 0x000000);
+    grid.material.opacity = 0.2;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    // model
+
+    const loader = new GLTFLoader();
+    loader.load(
+        'models/gltf/RobotExpressive/RobotExpressive.glb',
+        function (gltf) {
+            model = gltf.scene;
+            scene.add(model);
+
+            createGUI(model, gltf.animations);
+        },
+        undefined,
+        function (e) {
+            console.error(e);
+        },
+    );
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // stats
+    stats = new Stats();
+    container.appendChild(stats.dom);
+}
+
+function createGUI(model, animations) {
+    const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
+    const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];
+
+    gui = new GUI();
+
+    mixer = new THREE.AnimationMixer(model);
+
+    actions = {};
+
+    for (let i = 0; i < animations.length; i++) {
+        const clip = animations[i];
+        const action = mixer.clipAction(clip);
+        actions[clip.name] = action;
+
+        if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) {
+            action.clampWhenFinished = true;
+            action.loop = THREE.LoopOnce;
+        }
+    }
+
+    // states
+
+    const statesFolder = gui.addFolder('States');
+
+    const clipCtrl = statesFolder.add(api, 'state').options(states);
+
+    clipCtrl.onChange(function () {
+        fadeToAction(api.state, 0.5);
+    });
+
+    statesFolder.open();
+
+    // emotes
+
+    const emoteFolder = gui.addFolder('Emotes');
+
+    function createEmoteCallback(name) {
+        api[name] = function () {
+            fadeToAction(name, 0.2);
+
+            mixer.addEventListener('finished', restoreState);
+        };
+
+        emoteFolder.add(api, name);
+    }
+
+    function restoreState() {
+        mixer.removeEventListener('finished', restoreState);
+
+        fadeToAction(api.state, 0.2);
+    }
+
+    for (let i = 0; i < emotes.length; i++) {
+        createEmoteCallback(emotes[i]);
+    }
+
+    emoteFolder.open();
+
+    // expressions
+
+    face = model.getObjectByName('Head_4');
+
+    const expressions = Object.keys(face.morphTargetDictionary);
+    const expressionFolder = gui.addFolder('Expressions');
+
+    for (let i = 0; i < expressions.length; i++) {
+        expressionFolder.add(face.morphTargetInfluences, i, 0, 1, 0.01).name(expressions[i]);
+    }
+
+    activeAction = actions['Walking'];
+    activeAction.play();
+
+    expressionFolder.open();
+}
+
+function fadeToAction(name, duration) {
+    previousAction = activeAction;
+    activeAction = actions[name];
+
+    if (previousAction !== activeAction) {
+        previousAction.fadeOut(duration);
+    }
+
+    activeAction.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn(duration).play();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const dt = clock.getDelta();
+
+    if (mixer) mixer.update(dt);
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry.ts b/examples-testing/examples/webgl_buffergeometry.ts
new file mode 100644
index 000000000..28b2c96a4
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry.ts
@@ -0,0 +1,178 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let mesh;
+
+init();
+animate();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
+    light1.position.set(1, 1, 1);
+    scene.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
+    light2.position.set(0, -1, 0);
+    scene.add(light2);
+
+    //
+
+    const triangles = 160000;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const normals = [];
+    const colors = [];
+
+    const color = new THREE.Color();
+
+    const n = 800,
+        n2 = n / 2; // triangles spread in the cube
+    const d = 12,
+        d2 = d / 2; // individual triangle size
+
+    const pA = new THREE.Vector3();
+    const pB = new THREE.Vector3();
+    const pC = new THREE.Vector3();
+
+    const cb = new THREE.Vector3();
+    const ab = new THREE.Vector3();
+
+    for (let i = 0; i < triangles; i++) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        const ax = x + Math.random() * d - d2;
+        const ay = y + Math.random() * d - d2;
+        const az = z + Math.random() * d - d2;
+
+        const bx = x + Math.random() * d - d2;
+        const by = y + Math.random() * d - d2;
+        const bz = z + Math.random() * d - d2;
+
+        const cx = x + Math.random() * d - d2;
+        const cy = y + Math.random() * d - d2;
+        const cz = z + Math.random() * d - d2;
+
+        positions.push(ax, ay, az);
+        positions.push(bx, by, bz);
+        positions.push(cx, cy, cz);
+
+        // flat face normals
+
+        pA.set(ax, ay, az);
+        pB.set(bx, by, bz);
+        pC.set(cx, cy, cz);
+
+        cb.subVectors(pC, pB);
+        ab.subVectors(pA, pB);
+        cb.cross(ab);
+
+        cb.normalize();
+
+        const nx = cb.x;
+        const ny = cb.y;
+        const nz = cb.z;
+
+        normals.push(nx, ny, nz);
+        normals.push(nx, ny, nz);
+        normals.push(nx, ny, nz);
+
+        // colors
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz);
+
+        const alpha = Math.random();
+
+        colors.push(color.r, color.g, color.b, alpha);
+        colors.push(color.r, color.g, color.b, alpha);
+        colors.push(color.r, color.g, color.b, alpha);
+    }
+
+    function disposeArray() {
+        this.array = null;
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3).onUpload(disposeArray));
+    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3).onUpload(disposeArray));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4).onUpload(disposeArray));
+
+    geometry.computeBoundingSphere();
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xd5d5d5,
+        specular: 0xffffff,
+        shininess: 250,
+        side: THREE.DoubleSide,
+        vertexColors: true,
+        transparent: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.25;
+    mesh.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts b/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
new file mode 100644
index 000000000..96926c2c3
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
@@ -0,0 +1,113 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, mesh;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 2500;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    // geometry
+
+    const triangles = 10000;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const uvs = [];
+    const textureIndices = [];
+
+    const n = 800,
+        n2 = n / 2; // triangles spread in the cube
+    const d = 50,
+        d2 = d / 2; // individual triangle size
+
+    for (let i = 0; i < triangles; i++) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        const ax = x + Math.random() * d - d2;
+        const ay = y + Math.random() * d - d2;
+        const az = z + Math.random() * d - d2;
+
+        const bx = x + Math.random() * d - d2;
+        const by = y + Math.random() * d - d2;
+        const bz = z + Math.random() * d - d2;
+
+        const cx = x + Math.random() * d - d2;
+        const cy = y + Math.random() * d - d2;
+        const cz = z + Math.random() * d - d2;
+
+        positions.push(ax, ay, az);
+        positions.push(bx, by, bz);
+        positions.push(cx, cy, cz);
+
+        // uvs
+
+        uvs.push(0, 0);
+        uvs.push(0.5, 1);
+        uvs.push(1, 0);
+
+        // texture indices
+
+        const t = i % 3;
+        textureIndices.push(t, t, t);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
+    geometry.setAttribute('textureIndex', new THREE.Int16BufferAttribute(textureIndices, 1));
+    geometry.attributes.textureIndex.gpuType = THREE.IntType;
+
+    geometry.computeBoundingSphere();
+
+    // material
+
+    const loader = new THREE.TextureLoader();
+
+    const map1 = loader.load('textures/crate.gif');
+    const map2 = loader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
+    const map3 = loader.load('textures/terrain/grasslight-big.jpg');
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            uTextures: {
+                value: [map1, map2, map3],
+            },
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        side: THREE.DoubleSide,
+        glslVersion: THREE.GLSL3,
+    });
+
+    // mesh
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+}
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.25;
+    mesh.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_attributes_none.ts b/examples-testing/examples/webgl_buffergeometry_attributes_none.ts
new file mode 100644
index 000000000..a1424e871
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_attributes_none.ts
@@ -0,0 +1,56 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, mesh;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 4;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    // geometry
+
+    const triangleCount = 10000;
+    const vertexCountPerTriangle = 3;
+    const vertexCount = triangleCount * vertexCountPerTriangle;
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setDrawRange(0, vertexCount);
+
+    // material
+
+    const material = new THREE.RawShaderMaterial({
+        uniforms: {
+            seed: { value: 42 },
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        side: THREE.DoubleSide,
+        glslVersion: THREE.GLSL3,
+    });
+
+    // mesh
+
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.frustumCulled = false;
+    scene.add(mesh);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+}
+
+function animate(time) {
+    mesh.rotation.x = (time / 1000.0) * 0.25;
+    mesh.rotation.y = (time / 1000.0) * 0.5;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts b/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
new file mode 100644
index 000000000..0dffa65cc
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
@@ -0,0 +1,103 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let renderer, scene, camera, stats;
+
+let particleSystem, uniforms, geometry;
+
+const particles = 100000;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+
+    uniforms = {
+        pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/spark1.png') },
+    };
+
+    const shaderMaterial = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+
+        blending: THREE.AdditiveBlending,
+        depthTest: false,
+        transparent: true,
+        vertexColors: true,
+    });
+
+    const radius = 200;
+
+    geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const colors = [];
+    const sizes = [];
+
+    const color = new THREE.Color();
+
+    for (let i = 0; i < particles; i++) {
+        positions.push((Math.random() * 2 - 1) * radius);
+        positions.push((Math.random() * 2 - 1) * radius);
+        positions.push((Math.random() * 2 - 1) * radius);
+
+        color.setHSL(i / particles, 1.0, 0.5);
+
+        colors.push(color.r, color.g, color.b);
+
+        sizes.push(20);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1).setUsage(THREE.DynamicDrawUsage));
+
+    particleSystem = new THREE.Points(geometry, shaderMaterial);
+
+    scene.add(particleSystem);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = Date.now() * 0.005;
+
+    particleSystem.rotation.z = 0.01 * time;
+
+    const sizes = geometry.attributes.size.array;
+
+    for (let i = 0; i < particles; i++) {
+        sizes[i] = 10 * (1 + Math.sin(0.1 * i + time));
+    }
+
+    geometry.attributes.size.needsUpdate = true;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_drawrange.ts b/examples-testing/examples/webgl_buffergeometry_drawrange.ts
new file mode 100644
index 000000000..142ff43bf
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_drawrange.ts
@@ -0,0 +1,239 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let group;
+let container, stats;
+const particlesData = [];
+let camera, scene, renderer;
+let positions, colors;
+let particles;
+let pointCloud;
+let particlePositions;
+let linesMesh;
+
+const maxParticleCount = 1000;
+let particleCount = 500;
+const r = 800;
+const rHalf = r / 2;
+
+const effectController = {
+    showDots: true,
+    showLines: true,
+    minDistance: 150,
+    limitConnections: false,
+    maxConnections: 20,
+    particleCount: 500,
+};
+
+init();
+
+function initGUI() {
+    const gui = new GUI();
+
+    gui.add(effectController, 'showDots').onChange(function (value) {
+        pointCloud.visible = value;
+    });
+    gui.add(effectController, 'showLines').onChange(function (value) {
+        linesMesh.visible = value;
+    });
+    gui.add(effectController, 'minDistance', 10, 300);
+    gui.add(effectController, 'limitConnections');
+    gui.add(effectController, 'maxConnections', 0, 30, 1);
+    gui.add(effectController, 'particleCount', 0, maxParticleCount, 1).onChange(function (value) {
+        particleCount = value;
+        particles.setDrawRange(0, particleCount);
+    });
+}
+
+function init() {
+    initGUI();
+
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
+    camera.position.z = 1750;
+
+    const controls = new OrbitControls(camera, container);
+    controls.minDistance = 1000;
+    controls.maxDistance = 3000;
+
+    scene = new THREE.Scene();
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const helper = new THREE.BoxHelper(new THREE.Mesh(new THREE.BoxGeometry(r, r, r)));
+    helper.material.color.setHex(0x474747);
+    helper.material.blending = THREE.AdditiveBlending;
+    helper.material.transparent = true;
+    group.add(helper);
+
+    const segments = maxParticleCount * maxParticleCount;
+
+    positions = new Float32Array(segments * 3);
+    colors = new Float32Array(segments * 3);
+
+    const pMaterial = new THREE.PointsMaterial({
+        color: 0xffffff,
+        size: 3,
+        blending: THREE.AdditiveBlending,
+        transparent: true,
+        sizeAttenuation: false,
+    });
+
+    particles = new THREE.BufferGeometry();
+    particlePositions = new Float32Array(maxParticleCount * 3);
+
+    for (let i = 0; i < maxParticleCount; i++) {
+        const x = Math.random() * r - r / 2;
+        const y = Math.random() * r - r / 2;
+        const z = Math.random() * r - r / 2;
+
+        particlePositions[i * 3] = x;
+        particlePositions[i * 3 + 1] = y;
+        particlePositions[i * 3 + 2] = z;
+
+        // add it to the geometry
+        particlesData.push({
+            velocity: new THREE.Vector3(-1 + Math.random() * 2, -1 + Math.random() * 2, -1 + Math.random() * 2),
+            numConnections: 0,
+        });
+    }
+
+    particles.setDrawRange(0, particleCount);
+    particles.setAttribute(
+        'position',
+        new THREE.BufferAttribute(particlePositions, 3).setUsage(THREE.DynamicDrawUsage),
+    );
+
+    // create the particle system
+    pointCloud = new THREE.Points(particles, pMaterial);
+    group.add(pointCloud);
+
+    const geometry = new THREE.BufferGeometry();
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3).setUsage(THREE.DynamicDrawUsage));
+    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage));
+
+    geometry.computeBoundingSphere();
+
+    geometry.setDrawRange(0, 0);
+
+    const material = new THREE.LineBasicMaterial({
+        vertexColors: true,
+        blending: THREE.AdditiveBlending,
+        transparent: true,
+    });
+
+    linesMesh = new THREE.LineSegments(geometry, material);
+    group.add(linesMesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    let vertexpos = 0;
+    let colorpos = 0;
+    let numConnected = 0;
+
+    for (let i = 0; i < particleCount; i++) particlesData[i].numConnections = 0;
+
+    for (let i = 0; i < particleCount; i++) {
+        // get the particle
+        const particleData = particlesData[i];
+
+        particlePositions[i * 3] += particleData.velocity.x;
+        particlePositions[i * 3 + 1] += particleData.velocity.y;
+        particlePositions[i * 3 + 2] += particleData.velocity.z;
+
+        if (particlePositions[i * 3 + 1] < -rHalf || particlePositions[i * 3 + 1] > rHalf)
+            particleData.velocity.y = -particleData.velocity.y;
+
+        if (particlePositions[i * 3] < -rHalf || particlePositions[i * 3] > rHalf)
+            particleData.velocity.x = -particleData.velocity.x;
+
+        if (particlePositions[i * 3 + 2] < -rHalf || particlePositions[i * 3 + 2] > rHalf)
+            particleData.velocity.z = -particleData.velocity.z;
+
+        if (effectController.limitConnections && particleData.numConnections >= effectController.maxConnections)
+            continue;
+
+        // Check collision
+        for (let j = i + 1; j < particleCount; j++) {
+            const particleDataB = particlesData[j];
+            if (effectController.limitConnections && particleDataB.numConnections >= effectController.maxConnections)
+                continue;
+
+            const dx = particlePositions[i * 3] - particlePositions[j * 3];
+            const dy = particlePositions[i * 3 + 1] - particlePositions[j * 3 + 1];
+            const dz = particlePositions[i * 3 + 2] - particlePositions[j * 3 + 2];
+            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
+
+            if (dist < effectController.minDistance) {
+                particleData.numConnections++;
+                particleDataB.numConnections++;
+
+                const alpha = 1.0 - dist / effectController.minDistance;
+
+                positions[vertexpos++] = particlePositions[i * 3];
+                positions[vertexpos++] = particlePositions[i * 3 + 1];
+                positions[vertexpos++] = particlePositions[i * 3 + 2];
+
+                positions[vertexpos++] = particlePositions[j * 3];
+                positions[vertexpos++] = particlePositions[j * 3 + 1];
+                positions[vertexpos++] = particlePositions[j * 3 + 2];
+
+                colors[colorpos++] = alpha;
+                colors[colorpos++] = alpha;
+                colors[colorpos++] = alpha;
+
+                colors[colorpos++] = alpha;
+                colors[colorpos++] = alpha;
+                colors[colorpos++] = alpha;
+
+                numConnected++;
+            }
+        }
+    }
+
+    linesMesh.geometry.setDrawRange(0, numConnected * 2);
+    linesMesh.geometry.attributes.position.needsUpdate = true;
+    linesMesh.geometry.attributes.color.needsUpdate = true;
+
+    pointCloud.geometry.attributes.position.needsUpdate = true;
+
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.001;
+
+    group.rotation.y = time * 0.1;
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts b/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
new file mode 100644
index 000000000..aea462cf4
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let points;
+
+const particles = 300000;
+let drawCount = 10000;
+
+init();
+animate();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const positions2 = [];
+    const colors = [];
+
+    const color = new THREE.Color();
+
+    const n = 1000,
+        n2 = n / 2; // particles spread in the cube
+
+    for (let i = 0; i < particles; i++) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        positions.push(x, y, z);
+        positions2.push(z * 0.3, x * 0.3, y * 0.3);
+
+        // colors
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
+
+        colors.push(color.r, color.g, color.b);
+    }
+
+    const gl = renderer.getContext();
+
+    const pos = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, pos);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
+
+    const pos2 = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, pos2);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions2), gl.STATIC_DRAW);
+
+    const rgb = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, rgb);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
+
+    const posAttr1 = new THREE.GLBufferAttribute(pos, gl.FLOAT, 3, 4, particles);
+    const posAttr2 = new THREE.GLBufferAttribute(pos2, gl.FLOAT, 3, 4, particles);
+    geometry.setAttribute('position', posAttr1);
+
+    setInterval(function () {
+        const attr = geometry.getAttribute('position');
+
+        geometry.setAttribute('position', attr === posAttr1 ? posAttr2 : posAttr1);
+    }, 2000);
+
+    geometry.setAttribute('color', new THREE.GLBufferAttribute(rgb, gl.FLOAT, 3, 4, particles));
+
+    //
+
+    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
+
+    points = new THREE.Points(geometry, material);
+
+    geometry.boundingSphere = new THREE.Sphere().set(new THREE.Vector3(), 500);
+
+    scene.add(points);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    drawCount = (Math.max(5000, drawCount) + Math.floor(500 * Math.random())) % particles;
+    points.geometry.setDrawRange(0, drawCount);
+
+    const time = Date.now() * 0.001;
+
+    points.rotation.x = time * 0.1;
+    points.rotation.y = time * 0.2;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_indexed.ts b/examples-testing/examples/webgl_buffergeometry_indexed.ts
new file mode 100644
index 000000000..a2f9f3795
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_indexed.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats;
+
+let mesh;
+
+init();
+
+function init() {
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 64;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    //
+
+    const light = new THREE.HemisphereLight();
+    light.intensity = 3;
+    scene.add(light);
+
+    //
+
+    const geometry = new THREE.BufferGeometry();
+
+    const indices = [];
+
+    const vertices = [];
+    const normals = [];
+    const colors = [];
+
+    const size = 20;
+    const segments = 10;
+
+    const halfSize = size / 2;
+    const segmentSize = size / segments;
+
+    const _color = new THREE.Color();
+
+    // generate vertices, normals and color data for a simple grid geometry
+
+    for (let i = 0; i <= segments; i++) {
+        const y = i * segmentSize - halfSize;
+
+        for (let j = 0; j <= segments; j++) {
+            const x = j * segmentSize - halfSize;
+
+            vertices.push(x, -y, 0);
+            normals.push(0, 0, 1);
+
+            const r = x / size + 0.5;
+            const g = y / size + 0.5;
+
+            _color.setRGB(r, g, 1, THREE.SRGBColorSpace);
+
+            colors.push(_color.r, _color.g, _color.b);
+        }
+    }
+
+    // generate indices (data for element array buffer)
+
+    for (let i = 0; i < segments; i++) {
+        for (let j = 0; j < segments; j++) {
+            const a = i * (segments + 1) + (j + 1);
+            const b = i * (segments + 1) + j;
+            const c = (i + 1) * (segments + 1) + j;
+            const d = (i + 1) * (segments + 1) + (j + 1);
+
+            // generate two faces (triangles) per iteration
+
+            indices.push(a, b, d); // face one
+            indices.push(b, c, d); // face two
+        }
+    }
+
+    //
+
+    geometry.setIndex(indices);
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    const material = new THREE.MeshPhongMaterial({
+        side: THREE.DoubleSide,
+        vertexColors: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const gui = new GUI();
+    gui.add(material, 'wireframe');
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.25;
+    mesh.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing.ts b/examples-testing/examples/webgl_buffergeometry_instancing.ts
new file mode 100644
index 000000000..a5b90ae6e
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_instancing.ts
@@ -0,0 +1,138 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+
+    // geometry
+
+    const vector = new THREE.Vector4();
+
+    const instances = 50000;
+
+    const positions = [];
+    const offsets = [];
+    const colors = [];
+    const orientationsStart = [];
+    const orientationsEnd = [];
+
+    positions.push(0.025, -0.025, 0);
+    positions.push(-0.025, 0.025, 0);
+    positions.push(0, 0, 0.025);
+
+    // instanced attributes
+
+    for (let i = 0; i < instances; i++) {
+        // offsets
+
+        offsets.push(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
+
+        // colors
+
+        colors.push(Math.random(), Math.random(), Math.random(), Math.random());
+
+        // orientation start
+
+        vector.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
+        vector.normalize();
+
+        orientationsStart.push(vector.x, vector.y, vector.z, vector.w);
+
+        // orientation end
+
+        vector.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
+        vector.normalize();
+
+        orientationsEnd.push(vector.x, vector.y, vector.z, vector.w);
+    }
+
+    const geometry = new THREE.InstancedBufferGeometry();
+    geometry.instanceCount = instances; // set so its initalized for dat.GUI, will be set in first draw otherwise
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+
+    geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(new Float32Array(offsets), 3));
+    geometry.setAttribute('color', new THREE.InstancedBufferAttribute(new Float32Array(colors), 4));
+    geometry.setAttribute(
+        'orientationStart',
+        new THREE.InstancedBufferAttribute(new Float32Array(orientationsStart), 4),
+    );
+    geometry.setAttribute('orientationEnd', new THREE.InstancedBufferAttribute(new Float32Array(orientationsEnd), 4));
+
+    // material
+
+    const material = new THREE.RawShaderMaterial({
+        uniforms: {
+            time: { value: 1.0 },
+            sineTime: { value: 1.0 },
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        side: THREE.DoubleSide,
+        forceSinglePass: true,
+        transparent: true,
+    });
+
+    //
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const gui = new GUI({ width: 350 });
+    gui.add(geometry, 'instanceCount', 0, instances);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = performance.now();
+
+    const object = scene.children[0];
+
+    object.rotation.y = time * 0.0005;
+    object.material.uniforms['time'].value = time * 0.005;
+    object.material.uniforms['sineTime'].value = Math.sin(object.material.uniforms['time'].value * 0.05);
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts b/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
new file mode 100644
index 000000000..2158dff39
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
@@ -0,0 +1,86 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+let geometry, material, mesh;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.z = 1400;
+
+    scene = new THREE.Scene();
+
+    const circleGeometry = new THREE.CircleGeometry(1, 6);
+
+    geometry = new THREE.InstancedBufferGeometry();
+    geometry.index = circleGeometry.index;
+    geometry.attributes = circleGeometry.attributes;
+
+    const particleCount = 75000;
+
+    const translateArray = new Float32Array(particleCount * 3);
+
+    for (let i = 0, i3 = 0, l = particleCount; i < l; i++, i3 += 3) {
+        translateArray[i3 + 0] = Math.random() * 2 - 1;
+        translateArray[i3 + 1] = Math.random() * 2 - 1;
+        translateArray[i3 + 2] = Math.random() * 2 - 1;
+    }
+
+    geometry.setAttribute('translate', new THREE.InstancedBufferAttribute(translateArray, 3));
+
+    material = new THREE.RawShaderMaterial({
+        uniforms: {
+            map: { value: new THREE.TextureLoader().load('textures/sprites/circle.png') },
+            time: { value: 0.0 },
+        },
+        vertexShader: document.getElementById('vshader').textContent,
+        fragmentShader: document.getElementById('fshader').textContent,
+        depthTest: true,
+        depthWrite: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.scale.set(500, 500, 500);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    return true;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = performance.now() * 0.0005;
+
+    material.uniforms['time'].value = time;
+
+    mesh.rotation.x = time * 0.2;
+    mesh.rotation.y = time * 0.4;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts b/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
new file mode 100644
index 000000000..bef2c264d
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
@@ -0,0 +1,152 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+let camera, scene, renderer, mesh;
+
+const instances = 5000;
+let lastTime = 0;
+
+const moveQ = new THREE.Quaternion(0.5, 0.5, 0.5, 0.0).normalize();
+const tmpQ = new THREE.Quaternion();
+const tmpM = new THREE.Matrix4();
+const currentM = new THREE.Matrix4();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x101010);
+
+    // geometry
+
+    const geometry = new THREE.InstancedBufferGeometry();
+
+    // per mesh data x,y,z,w,u,v,s,t for 4-element alignment
+    // only use x,y,z and u,v; but x, y, z, nx, ny, nz, u, v would be a good layout
+    const vertexBuffer = new THREE.InterleavedBuffer(
+        new Float32Array([
+            // Front
+            -1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 1, 0, 0, 1, -1, 1, 0, 1, 1, 0, 0,
+            // Back
+            1, 1, -1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0,
+            // Left
+            -1, 1, -1, 0, 1, 1, 0, 0, -1, 1, 1, 0, 1, 0, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0,
+            // Right
+            1, 1, 1, 0, 1, 0, 0, 0, 1, 1, -1, 0, 1, 1, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 1, 0, 0,
+            // Top
+            -1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 1, 0, 0, 1, 1, -1, 0, 1, 1, 0, 0,
+            // Bottom
+            1, -1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0,
+        ]),
+        8,
+    );
+
+    // Use vertexBuffer, starting at offset 0, 3 items in position attribute
+    const positions = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0);
+    geometry.setAttribute('position', positions);
+    // Use vertexBuffer, starting at offset 4, 2 items in uv attribute
+    const uvs = new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 4);
+    geometry.setAttribute('uv', uvs);
+
+    const indices = new Uint16Array([
+        0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 8, 10, 9, 10, 11, 9, 12, 14, 13, 14, 15, 13, 16, 17, 18, 18, 17, 19, 20, 21,
+        22, 22, 21, 23,
+    ]);
+
+    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
+
+    // material
+
+    const material = new THREE.MeshBasicMaterial();
+    material.map = new THREE.TextureLoader().load('textures/crate.gif');
+    material.map.colorSpace = THREE.SRGBColorSpace;
+    material.map.flipY = false;
+
+    // per instance data
+
+    const matrix = new THREE.Matrix4();
+    const offset = new THREE.Vector3();
+    const orientation = new THREE.Quaternion();
+    const scale = new THREE.Vector3(1, 1, 1);
+    let x, y, z, w;
+
+    mesh = new THREE.InstancedMesh(geometry, material, instances);
+
+    for (let i = 0; i < instances; i++) {
+        // offsets
+
+        x = Math.random() * 100 - 50;
+        y = Math.random() * 100 - 50;
+        z = Math.random() * 100 - 50;
+
+        offset.set(x, y, z).normalize();
+        offset.multiplyScalar(5); // move out at least 5 units from center in current direction
+        offset.set(x + offset.x, y + offset.y, z + offset.z);
+
+        // orientations
+
+        x = Math.random() * 2 - 1;
+        y = Math.random() * 2 - 1;
+        z = Math.random() * 2 - 1;
+        w = Math.random() * 2 - 1;
+
+        orientation.set(x, y, z, w).normalize();
+
+        matrix.compose(offset, orientation, scale);
+
+        mesh.setMatrixAt(i, matrix);
+    }
+
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = performance.now();
+
+    mesh.rotation.y = time * 0.00005;
+
+    const delta = (time - lastTime) / 5000;
+    tmpQ.set(moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1).normalize();
+    tmpM.makeRotationFromQuaternion(tmpQ);
+
+    for (let i = 0, il = instances; i < il; i++) {
+        mesh.getMatrixAt(i, currentM);
+        currentM.multiply(tmpM);
+        mesh.setMatrixAt(i, currentM);
+    }
+
+    mesh.instanceMatrix.needsUpdate = true;
+    mesh.computeBoundingSphere();
+
+    lastTime = time;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_lines.ts b/examples-testing/examples/webgl_buffergeometry_lines.ts
new file mode 100644
index 000000000..1aaa5ca4a
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_lines.ts
@@ -0,0 +1,118 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats, clock;
+
+let camera, scene, renderer;
+
+let line;
+
+const segments = 10000;
+const r = 800;
+let t = 0;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 4000);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    const geometry = new THREE.BufferGeometry();
+    const material = new THREE.LineBasicMaterial({ vertexColors: true });
+
+    const positions = [];
+    const colors = [];
+
+    for (let i = 0; i < segments; i++) {
+        const x = Math.random() * r - r / 2;
+        const y = Math.random() * r - r / 2;
+        const z = Math.random() * r - r / 2;
+
+        // positions
+
+        positions.push(x, y, z);
+
+        // colors
+
+        colors.push(x / r + 0.5);
+        colors.push(y / r + 0.5);
+        colors.push(z / r + 0.5);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+    generateMorphTargets(geometry);
+
+    geometry.computeBoundingSphere();
+
+    line = new THREE.Line(geometry, material);
+    scene.add(line);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const delta = clock.getDelta();
+    const time = clock.getElapsedTime();
+
+    line.rotation.x = time * 0.25;
+    line.rotation.y = time * 0.5;
+
+    t += delta * 0.5;
+    line.morphTargetInfluences[0] = Math.abs(Math.sin(t));
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
+
+function generateMorphTargets(geometry) {
+    const data = [];
+
+    for (let i = 0; i < segments; i++) {
+        const x = Math.random() * r - r / 2;
+        const y = Math.random() * r - r / 2;
+        const z = Math.random() * r - r / 2;
+
+        data.push(x, y, z);
+    }
+
+    const morphTarget = new THREE.Float32BufferAttribute(data, 3);
+    morphTarget.name = 'target1';
+
+    geometry.morphAttributes.position = [morphTarget];
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts b/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
new file mode 100644
index 000000000..58296087e
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
@@ -0,0 +1,179 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let parent_node;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 9000;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.BufferGeometry();
+    const material = new THREE.LineBasicMaterial({ vertexColors: true });
+
+    const indices = [];
+    const positions = [];
+    const colors = [];
+
+    let next_positions_index = 0;
+
+    //
+
+    const iteration_count = 4;
+    const rangle = (60 * Math.PI) / 180.0;
+
+    function add_vertex(v) {
+        positions.push(v.x, v.y, v.z);
+        colors.push(Math.random() * 0.5 + 0.5, Math.random() * 0.5 + 0.5, 1);
+
+        return next_positions_index++;
+    }
+
+    // simple Koch curve
+
+    function snowflake_iteration(p0, p4, depth) {
+        if (--depth < 0) {
+            const i = next_positions_index - 1; // p0 already there
+            add_vertex(p4);
+            indices.push(i, i + 1);
+
+            return;
+        }
+
+        const v = p4.clone().sub(p0);
+        const v_tier = v.clone().multiplyScalar(1 / 3);
+        const p1 = p0.clone().add(v_tier);
+
+        const angle = Math.atan2(v.y, v.x) + rangle;
+        const length = v_tier.length();
+        const p2 = p1.clone();
+        p2.x += Math.cos(angle) * length;
+        p2.y += Math.sin(angle) * length;
+
+        const p3 = p0.clone().add(v_tier).add(v_tier);
+
+        snowflake_iteration(p0, p1, depth);
+        snowflake_iteration(p1, p2, depth);
+        snowflake_iteration(p2, p3, depth);
+        snowflake_iteration(p3, p4, depth);
+    }
+
+    function snowflake(points, loop, x_offset) {
+        for (let iteration = 0; iteration != iteration_count; iteration++) {
+            add_vertex(points[0]);
+
+            for (let p_index = 0, p_count = points.length - 1; p_index != p_count; p_index++) {
+                snowflake_iteration(points[p_index], points[p_index + 1], iteration);
+            }
+
+            if (loop) snowflake_iteration(points[points.length - 1], points[0], iteration);
+
+            // translate input curve for next iteration
+
+            for (let p_index = 0, p_count = points.length; p_index != p_count; p_index++) {
+                points[p_index].x += x_offset;
+            }
+        }
+    }
+
+    let y = 0;
+
+    snowflake([new THREE.Vector3(0, y, 0), new THREE.Vector3(500, y, 0)], false, 600);
+
+    y += 600;
+    snowflake(
+        [new THREE.Vector3(0, y, 0), new THREE.Vector3(250, y + 400, 0), new THREE.Vector3(500, y, 0)],
+        true,
+        600,
+    );
+
+    y += 600;
+    snowflake(
+        [
+            new THREE.Vector3(0, y, 0),
+            new THREE.Vector3(500, y, 0),
+            new THREE.Vector3(500, y + 500, 0),
+            new THREE.Vector3(0, y + 500, 0),
+        ],
+        true,
+        600,
+    );
+
+    y += 1000;
+    snowflake(
+        [
+            new THREE.Vector3(250, y, 0),
+            new THREE.Vector3(500, y, 0),
+            new THREE.Vector3(250, y, 0),
+            new THREE.Vector3(250, y + 250, 0),
+            new THREE.Vector3(250, y, 0),
+            new THREE.Vector3(0, y, 0),
+            new THREE.Vector3(250, y, 0),
+            new THREE.Vector3(250, y - 250, 0),
+            new THREE.Vector3(250, y, 0),
+        ],
+        false,
+        600,
+    );
+
+    //
+
+    geometry.setIndex(indices);
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+    geometry.computeBoundingSphere();
+
+    const lineSegments = new THREE.LineSegments(geometry, material);
+    lineSegments.position.x -= 1200;
+    lineSegments.position.y -= 1200;
+
+    parent_node = new THREE.Object3D();
+    parent_node.add(lineSegments);
+
+    scene.add(parent_node);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    parent_node.rotation.z = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_points.ts b/examples-testing/examples/webgl_buffergeometry_points.ts
new file mode 100644
index 000000000..4547d9d08
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_points.ts
@@ -0,0 +1,109 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let points;
+
+init();
+animate();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    const particles = 500000;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const colors = [];
+
+    const color = new THREE.Color();
+
+    const n = 1000,
+        n2 = n / 2; // particles spread in the cube
+
+    for (let i = 0; i < particles; i++) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        positions.push(x, y, z);
+
+        // colors
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
+
+        colors.push(color.r, color.g, color.b);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    geometry.computeBoundingSphere();
+
+    //
+
+    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
+
+    points = new THREE.Points(geometry, material);
+    scene.add(points);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    points.rotation.x = time * 0.25;
+    points.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts b/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
new file mode 100644
index 000000000..93eed992e
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
@@ -0,0 +1,122 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let points;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    const particles = 500000;
+
+    const geometry = new THREE.BufferGeometry();
+
+    // create a generic buffer of binary data (a single particle has 16 bytes of data)
+
+    const arrayBuffer = new ArrayBuffer(particles * 16);
+
+    // the following typed arrays share the same buffer
+
+    const interleavedFloat32Buffer = new Float32Array(arrayBuffer);
+    const interleavedUint8Buffer = new Uint8Array(arrayBuffer);
+
+    //
+
+    const color = new THREE.Color();
+
+    const n = 1000,
+        n2 = n / 2; // particles spread in the cube
+
+    for (let i = 0; i < interleavedFloat32Buffer.length; i += 4) {
+        // position (first 12 bytes)
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        interleavedFloat32Buffer[i + 0] = x;
+        interleavedFloat32Buffer[i + 1] = y;
+        interleavedFloat32Buffer[i + 2] = z;
+
+        // color (last 4 bytes)
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
+
+        const j = (i + 3) * 4;
+
+        interleavedUint8Buffer[j + 0] = color.r * 255;
+        interleavedUint8Buffer[j + 1] = color.g * 255;
+        interleavedUint8Buffer[j + 2] = color.b * 255;
+        interleavedUint8Buffer[j + 3] = 0; // not needed
+    }
+
+    const interleavedBuffer32 = new THREE.InterleavedBuffer(interleavedFloat32Buffer, 4);
+    const interleavedBuffer8 = new THREE.InterleavedBuffer(interleavedUint8Buffer, 16);
+
+    geometry.setAttribute('position', new THREE.InterleavedBufferAttribute(interleavedBuffer32, 3, 0, false));
+    geometry.setAttribute('color', new THREE.InterleavedBufferAttribute(interleavedBuffer8, 3, 12, true));
+
+    //
+
+    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
+
+    points = new THREE.Points(geometry, material);
+    scene.add(points);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    points.rotation.x = time * 0.25;
+    points.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_rawshader.ts b/examples-testing/examples/webgl_buffergeometry_rawshader.ts
new file mode 100644
index 000000000..5bc113dc3
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_rawshader.ts
@@ -0,0 +1,97 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x101010);
+
+    // geometry
+    // nr of triangles with 3 vertices per triangle
+    const vertexCount = 200 * 3;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const colors = [];
+
+    for (let i = 0; i < vertexCount; i++) {
+        // adding x,y,z
+        positions.push(Math.random() - 0.5);
+        positions.push(Math.random() - 0.5);
+        positions.push(Math.random() - 0.5);
+
+        // adding r,g,b,a
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+    }
+
+    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
+    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 4);
+
+    colorAttribute.normalized = true; // this will map the buffer values to 0.0f - +1.0f in the shader
+
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('color', colorAttribute);
+
+    // material
+
+    const material = new THREE.RawShaderMaterial({
+        uniforms: {
+            time: { value: 1.0 },
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        side: THREE.DoubleSide,
+        transparent: true,
+    });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = performance.now();
+
+    const object = scene.children[0];
+
+    object.rotation.y = time * 0.0005;
+    object.material.uniforms.time.value = time * 0.005;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_selective_draw.ts b/examples-testing/examples/webgl_buffergeometry_selective_draw.ts
new file mode 100644
index 000000000..d07176c51
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_selective_draw.ts
@@ -0,0 +1,150 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+let geometry, mesh;
+const numLat = 100;
+const numLng = 200;
+let numLinesCulled = 0;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.z = 3.5;
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    addLines(1.0);
+
+    const hideLinesButton = document.getElementById('hideLines');
+    hideLinesButton.addEventListener('click', hideLines);
+
+    const showAllLinesButton = document.getElementById('showAllLines');
+    showAllLinesButton.addEventListener('click', showAllLines);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+}
+
+function addLines(radius) {
+    geometry = new THREE.BufferGeometry();
+    const linePositions = new Float32Array(numLat * numLng * 3 * 2);
+    const lineColors = new Float32Array(numLat * numLng * 3 * 2);
+    const visible = new Float32Array(numLat * numLng * 2);
+
+    for (let i = 0; i < numLat; ++i) {
+        for (let j = 0; j < numLng; ++j) {
+            const lat = (Math.random() * Math.PI) / 50.0 + (i / numLat) * Math.PI;
+            const lng = (Math.random() * Math.PI) / 50.0 + (j / numLng) * 2 * Math.PI;
+
+            const index = i * numLng + j;
+
+            linePositions[index * 6 + 0] = 0;
+            linePositions[index * 6 + 1] = 0;
+            linePositions[index * 6 + 2] = 0;
+            linePositions[index * 6 + 3] = radius * Math.sin(lat) * Math.cos(lng);
+            linePositions[index * 6 + 4] = radius * Math.cos(lat);
+            linePositions[index * 6 + 5] = radius * Math.sin(lat) * Math.sin(lng);
+
+            const color = new THREE.Color(0xffffff);
+
+            color.setHSL(lat / Math.PI, 1.0, 0.2);
+            lineColors[index * 6 + 0] = color.r;
+            lineColors[index * 6 + 1] = color.g;
+            lineColors[index * 6 + 2] = color.b;
+
+            color.setHSL(lat / Math.PI, 1.0, 0.7);
+            lineColors[index * 6 + 3] = color.r;
+            lineColors[index * 6 + 4] = color.g;
+            lineColors[index * 6 + 5] = color.b;
+
+            // non-0 is visible
+            visible[index * 2 + 0] = 1.0;
+            visible[index * 2 + 1] = 1.0;
+        }
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
+    geometry.setAttribute('vertColor', new THREE.BufferAttribute(lineColors, 3));
+    geometry.setAttribute('visible', new THREE.BufferAttribute(visible, 1));
+
+    geometry.computeBoundingSphere();
+
+    const shaderMaterial = new THREE.ShaderMaterial({
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    mesh = new THREE.LineSegments(geometry, shaderMaterial);
+    scene.add(mesh);
+
+    updateCount();
+}
+
+function updateCount() {
+    const str =
+        '1 draw call, ' +
+        numLat * numLng +
+        ' lines, ' +
+        numLinesCulled +
+        ' culled (<a target="_blank" href="http://callum.com">author</a>)';
+    document.getElementById('title').innerHTML = str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+}
+
+function hideLines() {
+    for (let i = 0; i < geometry.attributes.visible.array.length; i += 2) {
+        if (Math.random() > 0.75) {
+            if (geometry.attributes.visible.array[i + 0]) {
+                ++numLinesCulled;
+            }
+
+            geometry.attributes.visible.array[i + 0] = 0;
+            geometry.attributes.visible.array[i + 1] = 0;
+        }
+    }
+
+    geometry.attributes.visible.needsUpdate = true;
+
+    updateCount();
+}
+
+function showAllLines() {
+    numLinesCulled = 0;
+
+    for (let i = 0; i < geometry.attributes.visible.array.length; i += 2) {
+        geometry.attributes.visible.array[i + 0] = 1;
+        geometry.attributes.visible.array[i + 1] = 1;
+    }
+
+    geometry.attributes.visible.needsUpdate = true;
+
+    updateCount();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.25;
+    mesh.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_buffergeometry_uint.ts b/examples-testing/examples/webgl_buffergeometry_uint.ts
new file mode 100644
index 000000000..0b8df6ec7
--- /dev/null
+++ b/examples-testing/examples/webgl_buffergeometry_uint.ts
@@ -0,0 +1,177 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let mesh;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
+    light1.position.set(1, 1, 1);
+    scene.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
+    light2.position.set(0, -1, 0);
+    scene.add(light2);
+
+    //
+
+    const triangles = 500000;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const normals = [];
+    const colors = [];
+
+    const color = new THREE.Color();
+
+    const n = 800,
+        n2 = n / 2; // triangles spread in the cube
+    const d = 12,
+        d2 = d / 2; // individual triangle size
+
+    const pA = new THREE.Vector3();
+    const pB = new THREE.Vector3();
+    const pC = new THREE.Vector3();
+
+    const cb = new THREE.Vector3();
+    const ab = new THREE.Vector3();
+
+    for (let i = 0; i < triangles; i++) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        const ax = x + Math.random() * d - d2;
+        const ay = y + Math.random() * d - d2;
+        const az = z + Math.random() * d - d2;
+
+        const bx = x + Math.random() * d - d2;
+        const by = y + Math.random() * d - d2;
+        const bz = z + Math.random() * d - d2;
+
+        const cx = x + Math.random() * d - d2;
+        const cy = y + Math.random() * d - d2;
+        const cz = z + Math.random() * d - d2;
+
+        positions.push(ax, ay, az);
+        positions.push(bx, by, bz);
+        positions.push(cx, cy, cz);
+
+        // flat face normals
+
+        pA.set(ax, ay, az);
+        pB.set(bx, by, bz);
+        pC.set(cx, cy, cz);
+
+        cb.subVectors(pC, pB);
+        ab.subVectors(pA, pB);
+        cb.cross(ab);
+
+        cb.normalize();
+
+        const nx = cb.x;
+        const ny = cb.y;
+        const nz = cb.z;
+
+        normals.push(nx * 32767, ny * 32767, nz * 32767);
+        normals.push(nx * 32767, ny * 32767, nz * 32767);
+        normals.push(nx * 32767, ny * 32767, nz * 32767);
+
+        // colors
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz);
+
+        colors.push(color.r * 255, color.g * 255, color.b * 255);
+        colors.push(color.r * 255, color.g * 255, color.b * 255);
+        colors.push(color.r * 255, color.g * 255, color.b * 255);
+    }
+
+    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
+    const normalAttribute = new THREE.Int16BufferAttribute(normals, 3);
+    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 3);
+
+    normalAttribute.normalized = true; // this will map the buffer values to 0.0f - +1.0f in the shader
+    colorAttribute.normalized = true;
+
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('normal', normalAttribute);
+    geometry.setAttribute('color', colorAttribute);
+
+    geometry.computeBoundingSphere();
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xd5d5d5,
+        specular: 0xffffff,
+        shininess: 250,
+        side: THREE.DoubleSide,
+        vertexColors: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.25;
+    mesh.rotation.y = time * 0.5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_camera.ts b/examples-testing/examples/webgl_camera.ts
new file mode 100644
index 000000000..f3d663603
--- /dev/null
+++ b/examples-testing/examples/webgl_camera.ts
@@ -0,0 +1,218 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+let aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+
+let container, stats;
+let camera, scene, renderer, mesh;
+let cameraRig, activeCamera, activeHelper;
+let cameraPerspective, cameraOrtho;
+let cameraPerspectiveHelper, cameraOrthoHelper;
+const frustumSize = 600;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, 0.5 * aspect, 1, 10000);
+    camera.position.z = 2500;
+
+    cameraPerspective = new THREE.PerspectiveCamera(50, 0.5 * aspect, 150, 1000);
+
+    cameraPerspectiveHelper = new THREE.CameraHelper(cameraPerspective);
+    scene.add(cameraPerspectiveHelper);
+
+    //
+    cameraOrtho = new THREE.OrthographicCamera(
+        (0.5 * frustumSize * aspect) / -2,
+        (0.5 * frustumSize * aspect) / 2,
+        frustumSize / 2,
+        frustumSize / -2,
+        150,
+        1000,
+    );
+
+    cameraOrthoHelper = new THREE.CameraHelper(cameraOrtho);
+    scene.add(cameraOrthoHelper);
+
+    //
+
+    activeCamera = cameraPerspective;
+    activeHelper = cameraPerspectiveHelper;
+
+    // counteract different front orientation of cameras vs rig
+
+    cameraOrtho.rotation.y = Math.PI;
+    cameraPerspective.rotation.y = Math.PI;
+
+    cameraRig = new THREE.Group();
+
+    cameraRig.add(cameraPerspective);
+    cameraRig.add(cameraOrtho);
+
+    scene.add(cameraRig);
+
+    //
+
+    mesh = new THREE.Mesh(
+        new THREE.SphereGeometry(100, 16, 8),
+        new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }),
+    );
+    scene.add(mesh);
+
+    const mesh2 = new THREE.Mesh(
+        new THREE.SphereGeometry(50, 16, 8),
+        new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }),
+    );
+    mesh2.position.y = 150;
+    mesh.add(mesh2);
+
+    const mesh3 = new THREE.Mesh(
+        new THREE.SphereGeometry(5, 16, 8),
+        new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true }),
+    );
+    mesh3.position.z = 150;
+    cameraRig.add(mesh3);
+
+    //
+
+    const geometry = new THREE.BufferGeometry();
+    const vertices = [];
+
+    for (let i = 0; i < 10000; i++) {
+        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // x
+        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // y
+        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // z
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    const particles = new THREE.Points(geometry, new THREE.PointsMaterial({ color: 0x888888 }));
+    scene.add(particles);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    renderer.setScissorTest(true);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('keydown', onKeyDown);
+}
+
+//
+
+function onKeyDown(event) {
+    switch (event.keyCode) {
+        case 79 /*O*/:
+            activeCamera = cameraOrtho;
+            activeHelper = cameraOrthoHelper;
+
+            break;
+
+        case 80 /*P*/:
+            activeCamera = cameraPerspective;
+            activeHelper = cameraPerspectiveHelper;
+
+            break;
+    }
+}
+
+//
+
+function onWindowResize() {
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+    aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    camera.aspect = 0.5 * aspect;
+    camera.updateProjectionMatrix();
+
+    cameraPerspective.aspect = 0.5 * aspect;
+    cameraPerspective.updateProjectionMatrix();
+
+    cameraOrtho.left = (-0.5 * frustumSize * aspect) / 2;
+    cameraOrtho.right = (0.5 * frustumSize * aspect) / 2;
+    cameraOrtho.top = frustumSize / 2;
+    cameraOrtho.bottom = -frustumSize / 2;
+    cameraOrtho.updateProjectionMatrix();
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const r = Date.now() * 0.0005;
+
+    mesh.position.x = 700 * Math.cos(r);
+    mesh.position.z = 700 * Math.sin(r);
+    mesh.position.y = 700 * Math.sin(r);
+
+    mesh.children[0].position.x = 70 * Math.cos(2 * r);
+    mesh.children[0].position.z = 70 * Math.sin(r);
+
+    if (activeCamera === cameraPerspective) {
+        cameraPerspective.fov = 35 + 30 * Math.sin(0.5 * r);
+        cameraPerspective.far = mesh.position.length();
+        cameraPerspective.updateProjectionMatrix();
+
+        cameraPerspectiveHelper.update();
+        cameraPerspectiveHelper.visible = true;
+
+        cameraOrthoHelper.visible = false;
+    } else {
+        cameraOrtho.far = mesh.position.length();
+        cameraOrtho.updateProjectionMatrix();
+
+        cameraOrthoHelper.update();
+        cameraOrthoHelper.visible = true;
+
+        cameraPerspectiveHelper.visible = false;
+    }
+
+    cameraRig.lookAt(mesh.position);
+
+    //
+
+    activeHelper.visible = false;
+
+    renderer.setClearColor(0x000000, 1);
+    renderer.setScissor(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.setViewport(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.render(scene, activeCamera);
+
+    //
+
+    activeHelper.visible = true;
+
+    renderer.setClearColor(0x111111, 1);
+    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.setViewport(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_camera_array.ts b/examples-testing/examples/webgl_camera_array.ts
new file mode 100644
index 000000000..8b10e27cb
--- /dev/null
+++ b/examples-testing/examples/webgl_camera_array.ts
@@ -0,0 +1,104 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let mesh;
+const AMOUNT = 6;
+
+init();
+
+function init() {
+    const ASPECT_RATIO = window.innerWidth / window.innerHeight;
+
+    const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
+    const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
+
+    const cameras = [];
+
+    for (let y = 0; y < AMOUNT; y++) {
+        for (let x = 0; x < AMOUNT; x++) {
+            const subcamera = new THREE.PerspectiveCamera(40, ASPECT_RATIO, 0.1, 10);
+            subcamera.viewport = new THREE.Vector4(
+                Math.floor(x * WIDTH),
+                Math.floor(y * HEIGHT),
+                Math.ceil(WIDTH),
+                Math.ceil(HEIGHT),
+            );
+            subcamera.position.x = x / AMOUNT - 0.5;
+            subcamera.position.y = 0.5 - y / AMOUNT;
+            subcamera.position.z = 1.5;
+            subcamera.position.multiplyScalar(2);
+            subcamera.lookAt(0, 0, 0);
+            subcamera.updateMatrixWorld();
+            cameras.push(subcamera);
+        }
+    }
+
+    camera = new THREE.ArrayCamera(cameras);
+    camera.position.z = 3;
+
+    scene = new THREE.Scene();
+
+    scene.add(new THREE.AmbientLight(0x999999));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0.5, 0.5, 1);
+    light.castShadow = true;
+    light.shadow.camera.zoom = 4; // tighter shadow map
+    scene.add(light);
+
+    const geometryBackground = new THREE.PlaneGeometry(100, 100);
+    const materialBackground = new THREE.MeshPhongMaterial({ color: 0x000066 });
+
+    const background = new THREE.Mesh(geometryBackground, materialBackground);
+    background.receiveShadow = true;
+    background.position.set(0, 0, -1);
+    scene.add(background);
+
+    const geometryCylinder = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
+    const materialCylinder = new THREE.MeshPhongMaterial({ color: 0xff0000 });
+
+    mesh = new THREE.Mesh(geometryCylinder, materialCylinder);
+    mesh.castShadow = true;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const ASPECT_RATIO = window.innerWidth / window.innerHeight;
+    const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
+    const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
+
+    camera.aspect = ASPECT_RATIO;
+    camera.updateProjectionMatrix();
+
+    for (let y = 0; y < AMOUNT; y++) {
+        for (let x = 0; x < AMOUNT; x++) {
+            const subcamera = camera.cameras[AMOUNT * y + x];
+
+            subcamera.viewport.set(Math.floor(x * WIDTH), Math.floor(y * HEIGHT), Math.ceil(WIDTH), Math.ceil(HEIGHT));
+
+            subcamera.aspect = ASPECT_RATIO;
+            subcamera.updateProjectionMatrix();
+        }
+    }
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.rotation.x += 0.005;
+    mesh.rotation.z += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts b/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
new file mode 100644
index 000000000..f1d440004
--- /dev/null
+++ b/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
@@ -0,0 +1,248 @@
+import * as THREE from 'three';
+
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+// 1 micrometer to 100 billion light years in one scene, with 1 unit = 1 meter?  preposterous!  and yet...
+const NEAR = 1e-6,
+    FAR = 1e27;
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+let screensplit = 0.25,
+    screensplit_right = 0;
+const mouse = [0.5, 0.5];
+let zoompos = -100,
+    minzoomspeed = 0.015;
+let zoomspeed = minzoomspeed;
+
+let container, border, stats;
+const objects = {};
+
+// Generate a number of text labels, from 1µm in size up to 100,000,000 light years
+// Try to use some descriptive real-world examples of objects at each scale
+
+const labeldata = [
+    { size: 0.01, scale: 0.0001, label: 'microscopic (1µm)' }, // FIXME - triangulating text fails at this size, so we scale instead
+    { size: 0.01, scale: 0.1, label: 'minuscule (1mm)' },
+    { size: 0.01, scale: 1.0, label: 'tiny (1cm)' },
+    { size: 1, scale: 1.0, label: 'child-sized (1m)' },
+    { size: 10, scale: 1.0, label: 'tree-sized (10m)' },
+    { size: 100, scale: 1.0, label: 'building-sized (100m)' },
+    { size: 1000, scale: 1.0, label: 'medium (1km)' },
+    { size: 10000, scale: 1.0, label: 'city-sized (10km)' },
+    { size: 3400000, scale: 1.0, label: 'moon-sized (3,400 Km)' },
+    { size: 12000000, scale: 1.0, label: 'planet-sized (12,000 km)' },
+    { size: 1400000000, scale: 1.0, label: 'sun-sized (1,400,000 km)' },
+    { size: 7.47e12, scale: 1.0, label: 'solar system-sized (50Au)' },
+    { size: 9.4605284e15, scale: 1.0, label: 'gargantuan (1 light year)' },
+    { size: 3.08567758e16, scale: 1.0, label: 'ludicrous (1 parsec)' },
+    { size: 1e19, scale: 1.0, label: 'mind boggling (1000 light years)' },
+];
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    const loader = new FontLoader();
+    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
+        const scene = initScene(font);
+
+        // Initialize two copies of the same scene, one with normal z-buffer and one with logarithmic z-buffer
+        objects.normal = initView(scene, 'normal', false);
+        objects.logzbuf = initView(scene, 'logzbuf', true);
+
+        animate();
+    });
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
+    border = document.getElementById('renderer_border');
+    border.addEventListener('pointerdown', onBorderPointerDown);
+
+    window.addEventListener('mousemove', onMouseMove);
+    window.addEventListener('resize', onWindowResize);
+    window.addEventListener('wheel', onMouseWheel);
+}
+
+function initView(scene, name, logDepthBuf) {
+    const framecontainer = document.getElementById('container_' + name);
+
+    const camera = new THREE.PerspectiveCamera(50, (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT, NEAR, FAR);
+    scene.add(camera);
+
+    const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: logDepthBuf });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.domElement.style.position = 'relative';
+    renderer.domElement.id = 'renderer_' + name;
+    framecontainer.appendChild(renderer.domElement);
+
+    return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
+}
+
+function initScene(font) {
+    const scene = new THREE.Scene();
+
+    scene.add(new THREE.AmbientLight(0x777777));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(100, 100, 100);
+    scene.add(light);
+
+    const materialargs = {
+        color: 0xffffff,
+        specular: 0x050505,
+        shininess: 50,
+        emissive: 0x000000,
+    };
+
+    const geometry = new THREE.SphereGeometry(0.5, 24, 12);
+
+    for (let i = 0; i < labeldata.length; i++) {
+        const scale = labeldata[i].scale || 1;
+
+        const labelgeo = new TextGeometry(labeldata[i].label, {
+            font: font,
+            size: labeldata[i].size,
+            depth: labeldata[i].size / 2,
+        });
+
+        labelgeo.computeBoundingSphere();
+
+        // center text
+        labelgeo.translate(-labelgeo.boundingSphere.radius, 0, 0);
+
+        materialargs.color = new THREE.Color().setHSL(Math.random(), 0.5, 0.5);
+
+        const material = new THREE.MeshPhongMaterial(materialargs);
+
+        const group = new THREE.Group();
+        group.position.z = -labeldata[i].size * scale;
+        scene.add(group);
+
+        const textmesh = new THREE.Mesh(labelgeo, material);
+        textmesh.scale.set(scale, scale, scale);
+        textmesh.position.z = -labeldata[i].size * scale;
+        textmesh.position.y = (labeldata[i].size / 4) * scale;
+        group.add(textmesh);
+
+        const dotmesh = new THREE.Mesh(geometry, material);
+        dotmesh.position.y = (-labeldata[i].size / 4) * scale;
+        dotmesh.scale.multiplyScalar(labeldata[i].size * scale);
+        group.add(dotmesh);
+    }
+
+    return scene;
+}
+
+function updateRendererSizes() {
+    // Recalculate size for both renderers when screen size or split location changes
+
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    screensplit_right = 1 - screensplit;
+
+    objects.normal.renderer.setSize(screensplit * SCREEN_WIDTH, SCREEN_HEIGHT);
+    objects.normal.camera.aspect = (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT;
+    objects.normal.camera.updateProjectionMatrix();
+    objects.normal.camera.setViewOffset(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH * screensplit, SCREEN_HEIGHT);
+    objects.normal.container.style.width = screensplit * 100 + '%';
+
+    objects.logzbuf.renderer.setSize(screensplit_right * SCREEN_WIDTH, SCREEN_HEIGHT);
+    objects.logzbuf.camera.aspect = (screensplit_right * SCREEN_WIDTH) / SCREEN_HEIGHT;
+    objects.logzbuf.camera.updateProjectionMatrix();
+    objects.logzbuf.camera.setViewOffset(
+        SCREEN_WIDTH,
+        SCREEN_HEIGHT,
+        SCREEN_WIDTH * screensplit,
+        0,
+        SCREEN_WIDTH * screensplit_right,
+        SCREEN_HEIGHT,
+    );
+    objects.logzbuf.container.style.width = screensplit_right * 100 + '%';
+
+    border.style.left = screensplit * 100 + '%';
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+    render();
+}
+
+function render() {
+    // Put some limits on zooming
+    const minzoom = labeldata[0].size * labeldata[0].scale * 1;
+    const maxzoom = labeldata[labeldata.length - 1].size * labeldata[labeldata.length - 1].scale * 100;
+    let damping = Math.abs(zoomspeed) > minzoomspeed ? 0.95 : 1.0;
+
+    // Zoom out faster the further out you go
+    const zoom = THREE.MathUtils.clamp(Math.pow(Math.E, zoompos), minzoom, maxzoom);
+    zoompos = Math.log(zoom);
+
+    // Slow down quickly at the zoom limits
+    if ((zoom == minzoom && zoomspeed < 0) || (zoom == maxzoom && zoomspeed > 0)) {
+        damping = 0.85;
+    }
+
+    zoompos += zoomspeed;
+    zoomspeed *= damping;
+
+    objects.normal.camera.position.x = Math.sin(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
+    objects.normal.camera.position.y = Math.sin(0.25 * Math.PI * (mouse[1] - 0.5)) * zoom;
+    objects.normal.camera.position.z = Math.cos(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
+    objects.normal.camera.lookAt(objects.normal.scene.position);
+
+    // Clone camera settings across both scenes
+    objects.logzbuf.camera.position.copy(objects.normal.camera.position);
+    objects.logzbuf.camera.quaternion.copy(objects.normal.camera.quaternion);
+
+    // Update renderer sizes if the split has changed
+    if (screensplit_right != 1 - screensplit) {
+        updateRendererSizes();
+    }
+
+    objects.normal.renderer.render(objects.normal.scene, objects.normal.camera);
+    objects.logzbuf.renderer.render(objects.logzbuf.scene, objects.logzbuf.camera);
+
+    stats.update();
+}
+
+function onWindowResize() {
+    updateRendererSizes();
+}
+
+function onBorderPointerDown() {
+    // activate draggable window resizing bar
+    window.addEventListener('pointermove', onBorderPointerMove);
+    window.addEventListener('pointerup', onBorderPointerUp);
+}
+
+function onBorderPointerMove(ev) {
+    screensplit = Math.max(0, Math.min(1, ev.clientX / window.innerWidth));
+}
+
+function onBorderPointerUp() {
+    window.removeEventListener('pointermove', onBorderPointerMove);
+    window.removeEventListener('pointerup', onBorderPointerUp);
+}
+
+function onMouseMove(ev) {
+    mouse[0] = ev.clientX / window.innerWidth;
+    mouse[1] = ev.clientY / window.innerHeight;
+}
+
+function onMouseWheel(ev) {
+    const amount = ev.deltaY;
+    if (amount === 0) return;
+    const dir = amount / Math.abs(amount);
+    zoomspeed = dir / 10;
+
+    // Slow down default zoom speed after user starts zooming, to give them more control
+    minzoomspeed = 0.001;
+}
diff --git a/examples-testing/examples/webgl_clipculldistance.ts b/examples-testing/examples/webgl_clipculldistance.ts
new file mode 100644
index 000000000..a5fb54d47
--- /dev/null
+++ b/examples-testing/examples/webgl_clipculldistance.ts
@@ -0,0 +1,110 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, controls, clock, scene, renderer, stats;
+
+let material;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    if (renderer.extensions.has('WEBGL_clip_cull_distance') === false) {
+        document.getElementById('notSupported').style.display = '';
+        return;
+    }
+
+    const ext = renderer.getContext().getExtension('WEBGL_clip_cull_distance');
+    const gl = renderer.getContext();
+
+    gl.enable(ext.CLIP_DISTANCE0_WEBGL);
+
+    // geometry
+
+    const vertexCount = 200 * 3;
+
+    const geometry = new THREE.BufferGeometry();
+
+    const positions = [];
+    const colors = [];
+
+    for (let i = 0; i < vertexCount; i++) {
+        // adding x,y,z
+        positions.push(Math.random() - 0.5);
+        positions.push(Math.random() - 0.5);
+        positions.push(Math.random() - 0.5);
+
+        // adding r,g,b,a
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+        colors.push(Math.random() * 255);
+    }
+
+    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
+    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 4);
+    colorAttribute.normalized = true;
+
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('color', colorAttribute);
+
+    // material
+
+    material = new THREE.ShaderMaterial({
+        uniforms: {
+            time: { value: 1.0 },
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        side: THREE.DoubleSide,
+        transparent: true,
+        vertexColors: true,
+    });
+
+    material.extensions.clipCullDistance = true;
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+    stats.update();
+
+    material.uniforms.time.value = clock.getElapsedTime();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_clipping.ts b/examples-testing/examples/webgl_clipping.ts
new file mode 100644
index 000000000..cde10c7d1
--- /dev/null
+++ b/examples-testing/examples/webgl_clipping.ts
@@ -0,0 +1,195 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, startTime, object, stats;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
+
+    camera.position.set(0, 1.3, 3);
+
+    scene = new THREE.Scene();
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const spotLight = new THREE.SpotLight(0xffffff, 60);
+    spotLight.angle = Math.PI / 5;
+    spotLight.penumbra = 0.2;
+    spotLight.position.set(2, 3, 3);
+    spotLight.castShadow = true;
+    spotLight.shadow.camera.near = 3;
+    spotLight.shadow.camera.far = 10;
+    spotLight.shadow.mapSize.width = 1024;
+    spotLight.shadow.mapSize.height = 1024;
+    scene.add(spotLight);
+
+    const dirLight = new THREE.DirectionalLight(0x55505a, 3);
+    dirLight.position.set(0, 3, 0);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.near = 1;
+    dirLight.shadow.camera.far = 10;
+
+    dirLight.shadow.camera.right = 1;
+    dirLight.shadow.camera.left = -1;
+    dirLight.shadow.camera.top = 1;
+    dirLight.shadow.camera.bottom = -1;
+
+    dirLight.shadow.mapSize.width = 1024;
+    dirLight.shadow.mapSize.height = 1024;
+    scene.add(dirLight);
+
+    // ***** Clipping planes: *****
+
+    const localPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.8);
+    const globalPlane = new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0.1);
+
+    // Geometry
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0x80ee10,
+        shininess: 100,
+        side: THREE.DoubleSide,
+
+        // ***** Clipping setup (material): *****
+        clippingPlanes: [localPlane],
+        clipShadows: true,
+
+        alphaToCoverage: true,
+    });
+
+    const geometry = new THREE.TorusKnotGeometry(0.4, 0.08, 95, 20);
+
+    object = new THREE.Mesh(geometry, material);
+    object.castShadow = true;
+    scene.add(object);
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(9, 9, 1, 1),
+        new THREE.MeshPhongMaterial({ color: 0xa0adaf, shininess: 150 }),
+    );
+
+    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // Renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // ***** Clipping setup (renderer): *****
+    const globalPlanes = [globalPlane],
+        Empty = Object.freeze([]);
+    renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
+    renderer.localClippingEnabled = true;
+
+    // Stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // Controls
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 1, 0);
+    controls.update();
+
+    // GUI
+
+    const gui = new GUI(),
+        props = {
+            alphaToCoverage: true,
+        },
+        folderLocal = gui.addFolder('Local Clipping'),
+        propsLocal = {
+            get Enabled() {
+                return renderer.localClippingEnabled;
+            },
+            set Enabled(v) {
+                renderer.localClippingEnabled = v;
+            },
+
+            get Shadows() {
+                return material.clipShadows;
+            },
+            set Shadows(v) {
+                material.clipShadows = v;
+            },
+
+            get Plane() {
+                return localPlane.constant;
+            },
+            set Plane(v) {
+                localPlane.constant = v;
+            },
+        },
+        folderGlobal = gui.addFolder('Global Clipping'),
+        propsGlobal = {
+            get Enabled() {
+                return renderer.clippingPlanes !== Empty;
+            },
+            set Enabled(v) {
+                renderer.clippingPlanes = v ? globalPlanes : Empty;
+            },
+
+            get Plane() {
+                return globalPlane.constant;
+            },
+            set Plane(v) {
+                globalPlane.constant = v;
+            },
+        };
+
+    gui.add(props, 'alphaToCoverage').onChange(function (value) {
+        ground.material.alphaToCoverage = value;
+        ground.material.needsUpdate = true;
+
+        material.alphaToCoverage = value;
+        material.needsUpdate = true;
+    });
+    folderLocal.add(propsLocal, 'Enabled');
+    folderLocal.add(propsLocal, 'Shadows');
+    folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);
+
+    folderGlobal.add(propsGlobal, 'Enabled');
+    folderGlobal.add(propsGlobal, 'Plane', -0.4, 3);
+
+    // Start
+
+    startTime = Date.now();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const currentTime = Date.now();
+    const time = (currentTime - startTime) / 1000;
+
+    object.position.y = 0.8;
+    object.rotation.x = time * 0.5;
+    object.rotation.y = time * 0.2;
+    object.scale.setScalar(Math.cos(time) * 0.125 + 0.875);
+
+    stats.begin();
+    renderer.render(scene, camera);
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_clipping_advanced.ts b/examples-testing/examples/webgl_clipping_advanced.ts
new file mode 100644
index 000000000..614d710da
--- /dev/null
+++ b/examples-testing/examples/webgl_clipping_advanced.ts
@@ -0,0 +1,355 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+function planesFromMesh(vertices, indices) {
+    // creates a clipping volume from a convex triangular mesh
+    // specified by the arrays 'vertices' and 'indices'
+
+    const n = indices.length / 3,
+        result = new Array(n);
+
+    for (let i = 0, j = 0; i < n; ++i, j += 3) {
+        const a = vertices[indices[j]],
+            b = vertices[indices[j + 1]],
+            c = vertices[indices[j + 2]];
+
+        result[i] = new THREE.Plane().setFromCoplanarPoints(a, b, c);
+    }
+
+    return result;
+}
+
+function createPlanes(n) {
+    // creates an array of n uninitialized plane objects
+
+    const result = new Array(n);
+
+    for (let i = 0; i !== n; ++i) result[i] = new THREE.Plane();
+
+    return result;
+}
+
+function assignTransformedPlanes(planesOut, planesIn, matrix) {
+    // sets an array of existing planes to transformed 'planesIn'
+
+    for (let i = 0, n = planesIn.length; i !== n; ++i) planesOut[i].copy(planesIn[i]).applyMatrix4(matrix);
+}
+
+function cylindricalPlanes(n, innerRadius) {
+    const result = createPlanes(n);
+
+    for (let i = 0; i !== n; ++i) {
+        const plane = result[i],
+            angle = (i * Math.PI * 2) / n;
+
+        plane.normal.set(Math.cos(angle), 0, Math.sin(angle));
+
+        plane.constant = innerRadius;
+    }
+
+    return result;
+}
+
+const planeToMatrix = (function () {
+    // creates a matrix that aligns X/Y to a given plane
+
+    // temporaries:
+    const xAxis = new THREE.Vector3(),
+        yAxis = new THREE.Vector3(),
+        trans = new THREE.Vector3();
+
+    return function planeToMatrix(plane) {
+        const zAxis = plane.normal,
+            matrix = new THREE.Matrix4();
+
+        // Hughes & Moeller '99
+        // "Building an Orthonormal Basis from a Unit Vector."
+
+        if (Math.abs(zAxis.x) > Math.abs(zAxis.z)) {
+            yAxis.set(-zAxis.y, zAxis.x, 0);
+        } else {
+            yAxis.set(0, -zAxis.z, zAxis.y);
+        }
+
+        xAxis.crossVectors(yAxis.normalize(), zAxis);
+
+        plane.coplanarPoint(trans);
+        return matrix.set(
+            xAxis.x,
+            yAxis.x,
+            zAxis.x,
+            trans.x,
+            xAxis.y,
+            yAxis.y,
+            zAxis.y,
+            trans.y,
+            xAxis.z,
+            yAxis.z,
+            zAxis.z,
+            trans.z,
+            0,
+            0,
+            0,
+            1,
+        );
+    };
+})();
+
+// A regular tetrahedron for the clipping volume:
+
+const Vertices = [
+        new THREE.Vector3(+1, 0, +Math.SQRT1_2),
+        new THREE.Vector3(-1, 0, +Math.SQRT1_2),
+        new THREE.Vector3(0, +1, -Math.SQRT1_2),
+        new THREE.Vector3(0, -1, -Math.SQRT1_2),
+    ],
+    Indices = [0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2],
+    Planes = planesFromMesh(Vertices, Indices),
+    PlaneMatrices = Planes.map(planeToMatrix),
+    GlobalClippingPlanes = cylindricalPlanes(5, 2.5),
+    Empty = Object.freeze([]);
+
+let camera, scene, renderer, startTime, stats, object, clipMaterial, volumeVisualization, globalClippingPlanes;
+
+function init() {
+    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
+
+    camera.position.set(0, 1.5, 3);
+
+    scene = new THREE.Scene();
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0xffffff));
+
+    const spotLight = new THREE.SpotLight(0xffffff, 60);
+    spotLight.angle = Math.PI / 5;
+    spotLight.penumbra = 0.2;
+    spotLight.position.set(2, 3, 3);
+    spotLight.castShadow = true;
+    spotLight.shadow.camera.near = 3;
+    spotLight.shadow.camera.far = 10;
+    spotLight.shadow.mapSize.width = 1024;
+    spotLight.shadow.mapSize.height = 1024;
+    scene.add(spotLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
+    dirLight.position.set(0, 2, 0);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.near = 1;
+    dirLight.shadow.camera.far = 10;
+
+    dirLight.shadow.camera.right = 1;
+    dirLight.shadow.camera.left = -1;
+    dirLight.shadow.camera.top = 1;
+    dirLight.shadow.camera.bottom = -1;
+
+    dirLight.shadow.mapSize.width = 1024;
+    dirLight.shadow.mapSize.height = 1024;
+    scene.add(dirLight);
+
+    // Geometry
+
+    clipMaterial = new THREE.MeshPhongMaterial({
+        color: 0xee0a10,
+        shininess: 100,
+        side: THREE.DoubleSide,
+        // Clipping setup:
+        clippingPlanes: createPlanes(Planes.length),
+        clipShadows: true,
+    });
+
+    object = new THREE.Group();
+
+    const geometry = new THREE.BoxGeometry(0.18, 0.18, 0.18);
+
+    for (let z = -2; z <= 2; ++z)
+        for (let y = -2; y <= 2; ++y)
+            for (let x = -2; x <= 2; ++x) {
+                const mesh = new THREE.Mesh(geometry, clipMaterial);
+                mesh.position.set(x / 5, y / 5, z / 5);
+                mesh.castShadow = true;
+                object.add(mesh);
+            }
+
+    scene.add(object);
+
+    const planeGeometry = new THREE.PlaneGeometry(3, 3, 1, 1),
+        color = new THREE.Color();
+
+    volumeVisualization = new THREE.Group();
+    volumeVisualization.visible = false;
+
+    for (let i = 0, n = Planes.length; i !== n; ++i) {
+        const material = new THREE.MeshBasicMaterial({
+            color: color.setHSL(i / n, 0.5, 0.5).getHex(),
+            side: THREE.DoubleSide,
+
+            opacity: 0.2,
+            transparent: true,
+
+            // clip to the others to show the volume (wildly
+            // intersecting transparent planes look bad)
+            clippingPlanes: clipMaterial.clippingPlanes.filter(function (_, j) {
+                return j !== i;
+            }),
+
+            // no need to enable shadow clipping - the plane
+            // visualization does not cast shadows
+        });
+
+        const mesh = new THREE.Mesh(planeGeometry, material);
+        mesh.matrixAutoUpdate = false;
+
+        volumeVisualization.add(mesh);
+    }
+
+    scene.add(volumeVisualization);
+
+    const ground = new THREE.Mesh(
+        planeGeometry,
+        new THREE.MeshPhongMaterial({
+            color: 0xa0adaf,
+            shininess: 10,
+        }),
+    );
+    ground.rotation.x = -Math.PI / 2;
+    ground.scale.multiplyScalar(3);
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // Renderer
+
+    const container = document.body;
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    container.appendChild(renderer.domElement);
+    // Clipping setup:
+    globalClippingPlanes = createPlanes(GlobalClippingPlanes.length);
+    renderer.clippingPlanes = Empty;
+    renderer.localClippingEnabled = true;
+
+    window.addEventListener('resize', onWindowResize);
+
+    // Stats
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // Controls
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 8;
+    controls.target.set(0, 1, 0);
+    controls.update();
+
+    // GUI
+
+    const gui = new GUI(),
+        folder = gui.addFolder('Local Clipping'),
+        props = {
+            get Enabled() {
+                return renderer.localClippingEnabled;
+            },
+            set Enabled(v) {
+                renderer.localClippingEnabled = v;
+                if (!v) volumeVisualization.visible = false;
+            },
+
+            get Shadows() {
+                return clipMaterial.clipShadows;
+            },
+            set Shadows(v) {
+                clipMaterial.clipShadows = v;
+            },
+
+            get Visualize() {
+                return volumeVisualization.visible;
+            },
+            set Visualize(v) {
+                if (renderer.localClippingEnabled) volumeVisualization.visible = v;
+            },
+        };
+
+    folder.add(props, 'Enabled');
+    folder.add(props, 'Shadows');
+    folder.add(props, 'Visualize').listen();
+
+    gui.addFolder('Global Clipping').add(
+        {
+            get Enabled() {
+                return renderer.clippingPlanes !== Empty;
+            },
+            set Enabled(v) {
+                renderer.clippingPlanes = v ? globalClippingPlanes : Empty;
+            },
+        },
+        'Enabled',
+    );
+
+    // Start
+
+    startTime = Date.now();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function setObjectWorldMatrix(object, matrix) {
+    // set the orientation of an object based on a world matrix
+
+    const parent = object.parent;
+    scene.updateMatrixWorld();
+    object.matrix.copy(parent.matrixWorld).invert();
+    object.applyMatrix4(matrix);
+}
+
+const transform = new THREE.Matrix4(),
+    tmpMatrix = new THREE.Matrix4();
+
+function animate() {
+    const currentTime = Date.now(),
+        time = (currentTime - startTime) / 1000;
+
+    object.position.y = 1;
+    object.rotation.x = time * 0.5;
+    object.rotation.y = time * 0.2;
+
+    object.updateMatrix();
+    transform.copy(object.matrix);
+
+    const bouncy = Math.cos(time * 0.5) * 0.5 + 0.7;
+    transform.multiply(tmpMatrix.makeScale(bouncy, bouncy, bouncy));
+
+    assignTransformedPlanes(clipMaterial.clippingPlanes, Planes, transform);
+
+    const planeMeshes = volumeVisualization.children;
+
+    for (let i = 0, n = planeMeshes.length; i !== n; ++i) {
+        tmpMatrix.multiplyMatrices(transform, PlaneMatrices[i]);
+        setObjectWorldMatrix(planeMeshes[i], tmpMatrix);
+    }
+
+    transform.makeRotationY(time * 0.1);
+
+    assignTransformedPlanes(globalClippingPlanes, GlobalClippingPlanes, transform);
+
+    stats.begin();
+    renderer.render(scene, camera);
+    stats.end();
+}
+
+init();
diff --git a/examples-testing/examples/webgl_clipping_intersection.ts b/examples-testing/examples/webgl_clipping_intersection.ts
new file mode 100644
index 000000000..5f45e45df
--- /dev/null
+++ b/examples-testing/examples/webgl_clipping_intersection.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+const params = {
+    clipIntersection: true,
+    planeConstant: 0,
+    showHelpers: false,
+    alphaToCoverage: true,
+};
+
+const clipPlanes = [
+    new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
+    new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
+    new THREE.Plane(new THREE.Vector3(0, 0, -1), 0),
+];
+
+init();
+render();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.localClippingEnabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
+
+    camera.position.set(-1.5, 2.5, 3.0);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use only if there is no animation loop
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+    controls.enablePan = false;
+
+    const light = new THREE.HemisphereLight(0xffffff, 0x080808, 4.5);
+    light.position.set(-1.25, 1, 1.25);
+    scene.add(light);
+
+    //
+
+    const group = new THREE.Group();
+
+    for (let i = 1; i <= 30; i += 2) {
+        const geometry = new THREE.SphereGeometry(i / 30, 48, 24);
+
+        const material = new THREE.MeshPhongMaterial({
+            color: new THREE.Color().setHSL(Math.random(), 0.5, 0.5, THREE.SRGBColorSpace),
+            side: THREE.DoubleSide,
+            clippingPlanes: clipPlanes,
+            clipIntersection: params.clipIntersection,
+            alphaToCoverage: true,
+        });
+
+        group.add(new THREE.Mesh(geometry, material));
+    }
+
+    scene.add(group);
+
+    // helpers
+
+    const helpers = new THREE.Group();
+    helpers.add(new THREE.PlaneHelper(clipPlanes[0], 2, 0xff0000));
+    helpers.add(new THREE.PlaneHelper(clipPlanes[1], 2, 0x00ff00));
+    helpers.add(new THREE.PlaneHelper(clipPlanes[2], 2, 0x0000ff));
+    helpers.visible = false;
+    scene.add(helpers);
+
+    // gui
+
+    const gui = new GUI();
+
+    gui.add(params, 'alphaToCoverage').onChange(function (value) {
+        group.children.forEach(c => {
+            c.material.alphaToCoverage = Boolean(value);
+            c.material.needsUpdate = true;
+        });
+
+        render();
+    });
+
+    gui.add(params, 'clipIntersection')
+        .name('clip intersection')
+        .onChange(function (value) {
+            const children = group.children;
+
+            for (let i = 0; i < children.length; i++) {
+                children[i].material.clipIntersection = value;
+            }
+
+            render();
+        });
+
+    gui.add(params, 'planeConstant', -1, 1)
+        .step(0.01)
+        .name('plane constant')
+        .onChange(function (value) {
+            for (let j = 0; j < clipPlanes.length; j++) {
+                clipPlanes[j].constant = value;
+            }
+
+            render();
+        });
+
+    gui.add(params, 'showHelpers')
+        .name('show helpers')
+        .onChange(function (value) {
+            helpers.visible = value;
+
+            render();
+        });
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_clipping_stencil.ts b/examples-testing/examples/webgl_clipping_stencil.ts
new file mode 100644
index 000000000..ecb6b42b8
--- /dev/null
+++ b/examples-testing/examples/webgl_clipping_stencil.ts
@@ -0,0 +1,260 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, object, stats;
+let planes, planeObjects, planeHelpers;
+let clock;
+
+const params = {
+    animate: true,
+    planeX: {
+        constant: 0,
+        negated: false,
+        displayHelper: false,
+    },
+    planeY: {
+        constant: 0,
+        negated: false,
+        displayHelper: false,
+    },
+    planeZ: {
+        constant: 0,
+        negated: false,
+        displayHelper: false,
+    },
+};
+
+init();
+
+function createPlaneStencilGroup(geometry, plane, renderOrder) {
+    const group = new THREE.Group();
+    const baseMat = new THREE.MeshBasicMaterial();
+    baseMat.depthWrite = false;
+    baseMat.depthTest = false;
+    baseMat.colorWrite = false;
+    baseMat.stencilWrite = true;
+    baseMat.stencilFunc = THREE.AlwaysStencilFunc;
+
+    // back faces
+    const mat0 = baseMat.clone();
+    mat0.side = THREE.BackSide;
+    mat0.clippingPlanes = [plane];
+    mat0.stencilFail = THREE.IncrementWrapStencilOp;
+    mat0.stencilZFail = THREE.IncrementWrapStencilOp;
+    mat0.stencilZPass = THREE.IncrementWrapStencilOp;
+
+    const mesh0 = new THREE.Mesh(geometry, mat0);
+    mesh0.renderOrder = renderOrder;
+    group.add(mesh0);
+
+    // front faces
+    const mat1 = baseMat.clone();
+    mat1.side = THREE.FrontSide;
+    mat1.clippingPlanes = [plane];
+    mat1.stencilFail = THREE.DecrementWrapStencilOp;
+    mat1.stencilZFail = THREE.DecrementWrapStencilOp;
+    mat1.stencilZPass = THREE.DecrementWrapStencilOp;
+
+    const mesh1 = new THREE.Mesh(geometry, mat1);
+    mesh1.renderOrder = renderOrder;
+
+    group.add(mesh1);
+
+    return group;
+}
+
+function init() {
+    clock = new THREE.Clock();
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(2, 2, 2);
+
+    scene.add(new THREE.AmbientLight(0xffffff, 1.5));
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(5, 10, 7.5);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.right = 2;
+    dirLight.shadow.camera.left = -2;
+    dirLight.shadow.camera.top = 2;
+    dirLight.shadow.camera.bottom = -2;
+
+    dirLight.shadow.mapSize.width = 1024;
+    dirLight.shadow.mapSize.height = 1024;
+    scene.add(dirLight);
+
+    planes = [
+        new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0),
+        new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
+        new THREE.Plane(new THREE.Vector3(0, 0, -1), 0),
+    ];
+
+    planeHelpers = planes.map(p => new THREE.PlaneHelper(p, 2, 0xffffff));
+    planeHelpers.forEach(ph => {
+        ph.visible = false;
+        scene.add(ph);
+    });
+
+    const geometry = new THREE.TorusKnotGeometry(0.4, 0.15, 220, 60);
+    object = new THREE.Group();
+    scene.add(object);
+
+    // Set up clip plane rendering
+    planeObjects = [];
+    const planeGeom = new THREE.PlaneGeometry(4, 4);
+
+    for (let i = 0; i < 3; i++) {
+        const poGroup = new THREE.Group();
+        const plane = planes[i];
+        const stencilGroup = createPlaneStencilGroup(geometry, plane, i + 1);
+
+        // plane is clipped by the other clipping planes
+        const planeMat = new THREE.MeshStandardMaterial({
+            color: 0xe91e63,
+            metalness: 0.1,
+            roughness: 0.75,
+            clippingPlanes: planes.filter(p => p !== plane),
+
+            stencilWrite: true,
+            stencilRef: 0,
+            stencilFunc: THREE.NotEqualStencilFunc,
+            stencilFail: THREE.ReplaceStencilOp,
+            stencilZFail: THREE.ReplaceStencilOp,
+            stencilZPass: THREE.ReplaceStencilOp,
+        });
+        const po = new THREE.Mesh(planeGeom, planeMat);
+        po.onAfterRender = function (renderer) {
+            renderer.clearStencil();
+        };
+
+        po.renderOrder = i + 1.1;
+
+        object.add(stencilGroup);
+        poGroup.add(po);
+        planeObjects.push(po);
+        scene.add(poGroup);
+    }
+
+    const material = new THREE.MeshStandardMaterial({
+        color: 0xffc107,
+        metalness: 0.1,
+        roughness: 0.75,
+        clippingPlanes: planes,
+        clipShadows: true,
+        shadowSide: THREE.DoubleSide,
+    });
+
+    // add the color
+    const clippedColorFront = new THREE.Mesh(geometry, material);
+    clippedColorFront.castShadow = true;
+    clippedColorFront.renderOrder = 6;
+    object.add(clippedColorFront);
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(9, 9, 1, 1),
+        new THREE.ShadowMaterial({ color: 0x000000, opacity: 0.25, side: THREE.DoubleSide }),
+    );
+
+    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
+    ground.position.y = -1;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // Renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true, stencil: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setClearColor(0x263238);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.localClippingEnabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    // Stats
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    // Controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 20;
+    controls.update();
+
+    // GUI
+    const gui = new GUI();
+    gui.add(params, 'animate');
+
+    const planeX = gui.addFolder('planeX');
+    planeX.add(params.planeX, 'displayHelper').onChange(v => (planeHelpers[0].visible = v));
+    planeX
+        .add(params.planeX, 'constant')
+        .min(-1)
+        .max(1)
+        .onChange(d => (planes[0].constant = d));
+    planeX.add(params.planeX, 'negated').onChange(() => {
+        planes[0].negate();
+        params.planeX.constant = planes[0].constant;
+    });
+    planeX.open();
+
+    const planeY = gui.addFolder('planeY');
+    planeY.add(params.planeY, 'displayHelper').onChange(v => (planeHelpers[1].visible = v));
+    planeY
+        .add(params.planeY, 'constant')
+        .min(-1)
+        .max(1)
+        .onChange(d => (planes[1].constant = d));
+    planeY.add(params.planeY, 'negated').onChange(() => {
+        planes[1].negate();
+        params.planeY.constant = planes[1].constant;
+    });
+    planeY.open();
+
+    const planeZ = gui.addFolder('planeZ');
+    planeZ.add(params.planeZ, 'displayHelper').onChange(v => (planeHelpers[2].visible = v));
+    planeZ
+        .add(params.planeZ, 'constant')
+        .min(-1)
+        .max(1)
+        .onChange(d => (planes[2].constant = d));
+    planeZ.add(params.planeZ, 'negated').onChange(() => {
+        planes[2].negate();
+        params.planeZ.constant = planes[2].constant;
+    });
+    planeZ.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (params.animate) {
+        object.rotation.x += delta * 0.5;
+        object.rotation.y += delta * 0.2;
+    }
+
+    for (let i = 0; i < planeObjects.length; i++) {
+        const plane = planes[i];
+        const po = planeObjects[i];
+        plane.coplanarPoint(po.position);
+        po.lookAt(po.position.x - plane.normal.x, po.position.y - plane.normal.y, po.position.z - plane.normal.z);
+    }
+
+    stats.begin();
+    renderer.render(scene, camera);
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_custom_attributes.ts b/examples-testing/examples/webgl_custom_attributes.ts
new file mode 100644
index 000000000..0dc897748
--- /dev/null
+++ b/examples-testing/examples/webgl_custom_attributes.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let renderer, scene, camera, stats;
+
+let sphere, uniforms;
+
+let displacement, noise;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    uniforms = {
+        amplitude: { value: 1.0 },
+        color: { value: new THREE.Color(0xff2200) },
+        colorTexture: { value: new THREE.TextureLoader().load('textures/water.jpg') },
+    };
+
+    uniforms['colorTexture'].value.wrapS = uniforms['colorTexture'].value.wrapT = THREE.RepeatWrapping;
+
+    const shaderMaterial = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    const radius = 50,
+        segments = 128,
+        rings = 64;
+
+    const geometry = new THREE.SphereGeometry(radius, segments, rings);
+
+    displacement = new Float32Array(geometry.attributes.position.count);
+    noise = new Float32Array(geometry.attributes.position.count);
+
+    for (let i = 0; i < displacement.length; i++) {
+        noise[i] = Math.random() * 5;
+    }
+
+    geometry.setAttribute('displacement', new THREE.BufferAttribute(displacement, 1));
+
+    sphere = new THREE.Mesh(geometry, shaderMaterial);
+    scene.add(sphere);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.01;
+
+    sphere.rotation.y = sphere.rotation.z = 0.01 * time;
+
+    uniforms['amplitude'].value = 2.5 * Math.sin(sphere.rotation.y * 0.125);
+    uniforms['color'].value.offsetHSL(0.0005, 0, 0);
+
+    for (let i = 0; i < displacement.length; i++) {
+        displacement[i] = Math.sin(0.1 * i + time);
+
+        noise[i] += 0.5 * (0.5 - Math.random());
+        noise[i] = THREE.MathUtils.clamp(noise[i], -5, 5);
+
+        displacement[i] += noise[i];
+    }
+
+    sphere.geometry.attributes.displacement.needsUpdate = true;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_custom_attributes_lines.ts b/examples-testing/examples/webgl_custom_attributes_lines.ts
new file mode 100644
index 000000000..3e2454e92
--- /dev/null
+++ b/examples-testing/examples/webgl_custom_attributes_lines.ts
@@ -0,0 +1,121 @@
+import * as THREE from 'three';
+
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let renderer, scene, camera, stats;
+
+let line, uniforms;
+
+const loader = new FontLoader();
+loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
+    init(font);
+});
+
+function init(font) {
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    uniforms = {
+        amplitude: { value: 5.0 },
+        opacity: { value: 0.3 },
+        color: { value: new THREE.Color(0xffffff) },
+    };
+
+    const shaderMaterial = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+        blending: THREE.AdditiveBlending,
+        depthTest: false,
+        transparent: true,
+    });
+
+    const geometry = new TextGeometry('three.js', {
+        font: font,
+
+        size: 50,
+        depth: 15,
+        curveSegments: 10,
+
+        bevelThickness: 5,
+        bevelSize: 1.5,
+        bevelEnabled: true,
+        bevelSegments: 10,
+    });
+
+    geometry.center();
+
+    const count = geometry.attributes.position.count;
+
+    const displacement = new THREE.Float32BufferAttribute(count * 3, 3);
+    geometry.setAttribute('displacement', displacement);
+
+    const customColor = new THREE.Float32BufferAttribute(count * 3, 3);
+    geometry.setAttribute('customColor', customColor);
+
+    const color = new THREE.Color(0xffffff);
+
+    for (let i = 0, l = customColor.count; i < l; i++) {
+        color.setHSL(i / l, 0.5, 0.5);
+        color.toArray(customColor.array, i * customColor.itemSize);
+    }
+
+    line = new THREE.Line(geometry, shaderMaterial);
+    line.rotation.x = 0.2;
+    scene.add(line);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.001;
+
+    line.rotation.y = 0.25 * time;
+
+    uniforms.amplitude.value = Math.sin(0.5 * time);
+    uniforms.color.value.offsetHSL(0.0005, 0, 0);
+
+    const attributes = line.geometry.attributes;
+    const array = attributes.displacement.array;
+
+    for (let i = 0, l = array.length; i < l; i += 3) {
+        array[i] += 0.3 * (0.5 - Math.random());
+        array[i + 1] += 0.3 * (0.5 - Math.random());
+        array[i + 2] += 0.3 * (0.5 - Math.random());
+    }
+
+    attributes.displacement.needsUpdate = true;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_custom_attributes_points.ts b/examples-testing/examples/webgl_custom_attributes_points.ts
new file mode 100644
index 000000000..ae112980a
--- /dev/null
+++ b/examples-testing/examples/webgl_custom_attributes_points.ts
@@ -0,0 +1,117 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let renderer, scene, camera, stats;
+
+let sphere;
+
+const WIDTH = window.innerWidth;
+const HEIGHT = window.innerHeight;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 10000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+
+    const amount = 100000;
+    const radius = 200;
+
+    const positions = new Float32Array(amount * 3);
+    const colors = new Float32Array(amount * 3);
+    const sizes = new Float32Array(amount);
+
+    const vertex = new THREE.Vector3();
+    const color = new THREE.Color(0xffffff);
+
+    for (let i = 0; i < amount; i++) {
+        vertex.x = (Math.random() * 2 - 1) * radius;
+        vertex.y = (Math.random() * 2 - 1) * radius;
+        vertex.z = (Math.random() * 2 - 1) * radius;
+        vertex.toArray(positions, i * 3);
+
+        if (vertex.x < 0) {
+            color.setHSL(0.5 + 0.1 * (i / amount), 0.7, 0.5);
+        } else {
+            color.setHSL(0.0 + 0.1 * (i / amount), 0.9, 0.5);
+        }
+
+        color.toArray(colors, i * 3);
+
+        sizes[i] = 10;
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    geometry.setAttribute('customColor', new THREE.BufferAttribute(colors, 3));
+    geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
+
+    //
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            color: { value: new THREE.Color(0xffffff) },
+            pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/spark1.png') },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+
+        blending: THREE.AdditiveBlending,
+        depthTest: false,
+        transparent: true,
+    });
+
+    //
+
+    sphere = new THREE.Points(geometry, material);
+    scene.add(sphere);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(WIDTH, HEIGHT);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.005;
+
+    sphere.rotation.z = 0.01 * time;
+
+    const geometry = sphere.geometry;
+    const attributes = geometry.attributes;
+
+    for (let i = 0; i < attributes.size.array.length; i++) {
+        attributes.size.array[i] = 14 + 13 * Math.sin(0.1 * i + time);
+    }
+
+    attributes.size.needsUpdate = true;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_custom_attributes_points2.ts b/examples-testing/examples/webgl_custom_attributes_points2.ts
new file mode 100644
index 000000000..edd158fa1
--- /dev/null
+++ b/examples-testing/examples/webgl_custom_attributes_points2.ts
@@ -0,0 +1,193 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let renderer, scene, camera, stats;
+let sphere, length1;
+
+const WIDTH = window.innerWidth;
+const HEIGHT = window.innerHeight;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 1, 10000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+
+    const radius = 100,
+        segments = 68,
+        rings = 38;
+
+    let sphereGeometry = new THREE.SphereGeometry(radius, segments, rings);
+    let boxGeometry = new THREE.BoxGeometry(0.8 * radius, 0.8 * radius, 0.8 * radius, 10, 10, 10);
+
+    // if normal and uv attributes are not removed, mergeVertices() can't consolidate identical vertices with different normal/uv data
+
+    sphereGeometry.deleteAttribute('normal');
+    sphereGeometry.deleteAttribute('uv');
+
+    boxGeometry.deleteAttribute('normal');
+    boxGeometry.deleteAttribute('uv');
+
+    sphereGeometry = BufferGeometryUtils.mergeVertices(sphereGeometry);
+    boxGeometry = BufferGeometryUtils.mergeVertices(boxGeometry);
+
+    const combinedGeometry = BufferGeometryUtils.mergeGeometries([sphereGeometry, boxGeometry]);
+    const positionAttribute = combinedGeometry.getAttribute('position');
+
+    const colors = [];
+    const sizes = [];
+
+    const color = new THREE.Color();
+    const vertex = new THREE.Vector3();
+
+    length1 = sphereGeometry.getAttribute('position').count;
+
+    for (let i = 0, l = positionAttribute.count; i < l; i++) {
+        vertex.fromBufferAttribute(positionAttribute, i);
+
+        if (i < length1) {
+            color.setHSL(0.01 + 0.1 * (i / length1), 0.99, (vertex.y + radius) / (4 * radius));
+        } else {
+            color.setHSL(0.6, 0.75, 0.25 + vertex.y / (2 * radius));
+        }
+
+        color.toArray(colors, i * 3);
+
+        sizes[i] = i < length1 ? 10 : 40;
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
+    geometry.setAttribute('ca', new THREE.Float32BufferAttribute(colors, 3));
+
+    //
+
+    const texture = new THREE.TextureLoader().load('textures/sprites/disc.png');
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            color: { value: new THREE.Color(0xffffff) },
+            pointTexture: { value: texture },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+        transparent: true,
+    });
+
+    //
+
+    sphere = new THREE.Points(geometry, material);
+    scene.add(sphere);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(WIDTH, HEIGHT);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function sortPoints() {
+    const vector = new THREE.Vector3();
+
+    // Model View Projection matrix
+
+    const matrix = new THREE.Matrix4();
+    matrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
+    matrix.multiply(sphere.matrixWorld);
+
+    //
+
+    const geometry = sphere.geometry;
+
+    let index = geometry.getIndex();
+    const positions = geometry.getAttribute('position').array;
+    const length = positions.length / 3;
+
+    if (index === null) {
+        const array = new Uint16Array(length);
+
+        for (let i = 0; i < length; i++) {
+            array[i] = i;
+        }
+
+        index = new THREE.BufferAttribute(array, 1);
+
+        geometry.setIndex(index);
+    }
+
+    const sortArray = [];
+
+    for (let i = 0; i < length; i++) {
+        vector.fromArray(positions, i * 3);
+        vector.applyMatrix4(matrix);
+
+        sortArray.push([vector.z, i]);
+    }
+
+    function numericalSort(a, b) {
+        return b[0] - a[0];
+    }
+
+    sortArray.sort(numericalSort);
+
+    const indices = index.array;
+
+    for (let i = 0; i < length; i++) {
+        indices[i] = sortArray[i][1];
+    }
+
+    geometry.index.needsUpdate = true;
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.005;
+
+    sphere.rotation.y = 0.02 * time;
+    sphere.rotation.z = 0.02 * time;
+
+    const geometry = sphere.geometry;
+    const attributes = geometry.attributes;
+
+    for (let i = 0; i < attributes.size.array.length; i++) {
+        if (i < length1) {
+            attributes.size.array[i] = 16 + 12 * Math.sin(0.1 * i + time);
+        }
+    }
+
+    attributes.size.needsUpdate = true;
+
+    sortPoints();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_custom_attributes_points3.ts b/examples-testing/examples/webgl_custom_attributes_points3.ts
new file mode 100644
index 000000000..1b46a805e
--- /dev/null
+++ b/examples-testing/examples/webgl_custom_attributes_points3.ts
@@ -0,0 +1,200 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let renderer, scene, camera, stats;
+
+let object;
+
+let vertices1;
+
+const WIDTH = window.innerWidth;
+const HEIGHT = window.innerHeight;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 1000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    let radius = 100;
+    const inner = 0.6 * radius;
+    const vertex = new THREE.Vector3();
+    const vertices = [];
+
+    for (let i = 0; i < 100000; i++) {
+        vertex.x = Math.random() * 2 - 1;
+        vertex.y = Math.random() * 2 - 1;
+        vertex.z = Math.random() * 2 - 1;
+        vertex.multiplyScalar(radius);
+
+        if (
+            vertex.x > inner ||
+            vertex.x < -inner ||
+            vertex.y > inner ||
+            vertex.y < -inner ||
+            vertex.z > inner ||
+            vertex.z < -inner
+        )
+            vertices.push(vertex.x, vertex.y, vertex.z);
+    }
+
+    vertices1 = vertices.length / 3;
+
+    radius = 200;
+
+    let boxGeometry1 = new THREE.BoxGeometry(radius, 0.1 * radius, 0.1 * radius, 50, 5, 5);
+
+    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
+
+    boxGeometry1.deleteAttribute('normal');
+    boxGeometry1.deleteAttribute('uv');
+
+    boxGeometry1 = BufferGeometryUtils.mergeVertices(boxGeometry1);
+
+    const matrix = new THREE.Matrix4();
+    const position = new THREE.Vector3();
+    const rotation = new THREE.Euler();
+    const quaternion = new THREE.Quaternion();
+    const scale = new THREE.Vector3(1, 1, 1);
+
+    function addGeo(geo, x, y, z, ry) {
+        position.set(x, y, z);
+        rotation.set(0, ry, 0);
+
+        matrix.compose(position, quaternion.setFromEuler(rotation), scale);
+
+        const positionAttribute = geo.getAttribute('position');
+
+        for (let i = 0, l = positionAttribute.count; i < l; i++) {
+            vertex.fromBufferAttribute(positionAttribute, i);
+            vertex.applyMatrix4(matrix);
+            vertices.push(vertex.x, vertex.y, vertex.z);
+        }
+    }
+
+    // side 1
+
+    addGeo(boxGeometry1, 0, 110, 110, 0);
+    addGeo(boxGeometry1, 0, 110, -110, 0);
+    addGeo(boxGeometry1, 0, -110, 110, 0);
+    addGeo(boxGeometry1, 0, -110, -110, 0);
+
+    // side 2
+
+    addGeo(boxGeometry1, 110, 110, 0, Math.PI / 2);
+    addGeo(boxGeometry1, 110, -110, 0, Math.PI / 2);
+    addGeo(boxGeometry1, -110, 110, 0, Math.PI / 2);
+    addGeo(boxGeometry1, -110, -110, 0, Math.PI / 2);
+
+    // corner edges
+
+    let boxGeometry2 = new THREE.BoxGeometry(0.1 * radius, radius * 1.2, 0.1 * radius, 5, 60, 5);
+
+    boxGeometry2.deleteAttribute('normal');
+    boxGeometry2.deleteAttribute('uv');
+
+    boxGeometry2 = BufferGeometryUtils.mergeVertices(boxGeometry2);
+
+    addGeo(boxGeometry2, 110, 0, 110, 0);
+    addGeo(boxGeometry2, 110, 0, -110, 0);
+    addGeo(boxGeometry2, -110, 0, 110, 0);
+    addGeo(boxGeometry2, -110, 0, -110, 0);
+
+    const positionAttribute = new THREE.Float32BufferAttribute(vertices, 3);
+
+    const colors = [];
+    const sizes = [];
+
+    const color = new THREE.Color();
+
+    for (let i = 0; i < positionAttribute.count; i++) {
+        if (i < vertices1) {
+            color.setHSL(0.5 + 0.2 * (i / vertices1), 1, 0.5);
+        } else {
+            color.setHSL(0.1, 1, 0.5);
+        }
+
+        color.toArray(colors, i * 3);
+
+        sizes[i] = i < vertices1 ? 10 : 40;
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('ca', new THREE.Float32BufferAttribute(colors, 3));
+    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
+
+    //
+
+    const texture = new THREE.TextureLoader().load('textures/sprites/ball.png');
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            amplitude: { value: 1.0 },
+            color: { value: new THREE.Color(0xffffff) },
+            pointTexture: { value: texture },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    //
+
+    object = new THREE.Points(geometry, material);
+    scene.add(object);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(WIDTH, HEIGHT);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.01;
+
+    object.rotation.y = object.rotation.z = 0.02 * time;
+
+    const geometry = object.geometry;
+    const attributes = geometry.attributes;
+
+    for (let i = 0; i < attributes.size.array.length; i++) {
+        if (i < vertices1) {
+            attributes.size.array[i] = Math.max(0, 26 + 32 * Math.sin(0.1 * i + 0.6 * time));
+        }
+    }
+
+    attributes.size.needsUpdate = true;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_decals.ts b/examples-testing/examples/webgl_decals.ts
new file mode 100644
index 000000000..23cdb4da8
--- /dev/null
+++ b/examples-testing/examples/webgl_decals.ts
@@ -0,0 +1,237 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DecalGeometry } from 'three/addons/geometries/DecalGeometry.js';
+
+const container = document.getElementById('container');
+
+let renderer, scene, camera, stats;
+let mesh;
+let raycaster;
+let line;
+
+const intersection = {
+    intersects: false,
+    point: new THREE.Vector3(),
+    normal: new THREE.Vector3(),
+};
+const mouse = new THREE.Vector2();
+const intersects = [];
+
+const textureLoader = new THREE.TextureLoader();
+const decalDiffuse = textureLoader.load('textures/decal/decal-diffuse.png');
+decalDiffuse.colorSpace = THREE.SRGBColorSpace;
+const decalNormal = textureLoader.load('textures/decal/decal-normal.jpg');
+
+const decalMaterial = new THREE.MeshPhongMaterial({
+    specular: 0x444444,
+    map: decalDiffuse,
+    normalMap: decalNormal,
+    normalScale: new THREE.Vector2(1, 1),
+    shininess: 30,
+    transparent: true,
+    depthTest: true,
+    depthWrite: false,
+    polygonOffset: true,
+    polygonOffsetFactor: -4,
+    wireframe: false,
+});
+
+const decals = [];
+let mouseHelper;
+const position = new THREE.Vector3();
+const orientation = new THREE.Euler();
+const size = new THREE.Vector3(10, 10, 10);
+
+const params = {
+    minScale: 10,
+    maxScale: 20,
+    rotate: true,
+    clear: function () {
+        removeDecals();
+    },
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 120;
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 50;
+    controls.maxDistance = 200;
+
+    scene.add(new THREE.AmbientLight(0x666666));
+
+    const dirLight1 = new THREE.DirectionalLight(0xffddcc, 3);
+    dirLight1.position.set(1, 0.75, 0.5);
+    scene.add(dirLight1);
+
+    const dirLight2 = new THREE.DirectionalLight(0xccccff, 3);
+    dirLight2.position.set(-1, 0.75, -0.5);
+    scene.add(dirLight2);
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setFromPoints([new THREE.Vector3(), new THREE.Vector3()]);
+
+    line = new THREE.Line(geometry, new THREE.LineBasicMaterial());
+    scene.add(line);
+
+    loadLeePerrySmith();
+
+    raycaster = new THREE.Raycaster();
+
+    mouseHelper = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 10), new THREE.MeshNormalMaterial());
+    mouseHelper.visible = false;
+    scene.add(mouseHelper);
+
+    window.addEventListener('resize', onWindowResize);
+
+    let moved = false;
+
+    controls.addEventListener('change', function () {
+        moved = true;
+    });
+
+    window.addEventListener('pointerdown', function () {
+        moved = false;
+    });
+
+    window.addEventListener('pointerup', function (event) {
+        if (moved === false) {
+            checkIntersection(event.clientX, event.clientY);
+
+            if (intersection.intersects) shoot();
+        }
+    });
+
+    window.addEventListener('pointermove', onPointerMove);
+
+    function onPointerMove(event) {
+        if (event.isPrimary) {
+            checkIntersection(event.clientX, event.clientY);
+        }
+    }
+
+    function checkIntersection(x, y) {
+        if (mesh === undefined) return;
+
+        mouse.x = (x / window.innerWidth) * 2 - 1;
+        mouse.y = -(y / window.innerHeight) * 2 + 1;
+
+        raycaster.setFromCamera(mouse, camera);
+        raycaster.intersectObject(mesh, false, intersects);
+
+        if (intersects.length > 0) {
+            const p = intersects[0].point;
+            mouseHelper.position.copy(p);
+            intersection.point.copy(p);
+
+            const n = intersects[0].face.normal.clone();
+            n.transformDirection(mesh.matrixWorld);
+            n.multiplyScalar(10);
+            n.add(intersects[0].point);
+
+            intersection.normal.copy(intersects[0].face.normal);
+            mouseHelper.lookAt(n);
+
+            const positions = line.geometry.attributes.position;
+            positions.setXYZ(0, p.x, p.y, p.z);
+            positions.setXYZ(1, n.x, n.y, n.z);
+            positions.needsUpdate = true;
+
+            intersection.intersects = true;
+
+            intersects.length = 0;
+        } else {
+            intersection.intersects = false;
+        }
+    }
+
+    const gui = new GUI();
+
+    gui.add(params, 'minScale', 1, 30);
+    gui.add(params, 'maxScale', 1, 30);
+    gui.add(params, 'rotate');
+    gui.add(params, 'clear');
+    gui.open();
+}
+
+function loadLeePerrySmith() {
+    const map = textureLoader.load('models/gltf/LeePerrySmith/Map-COL.jpg');
+    map.colorSpace = THREE.SRGBColorSpace;
+    const specularMap = textureLoader.load('models/gltf/LeePerrySmith/Map-SPEC.jpg');
+    const normalMap = textureLoader.load('models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg');
+
+    const loader = new GLTFLoader();
+
+    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        mesh = gltf.scene.children[0];
+        mesh.material = new THREE.MeshPhongMaterial({
+            specular: 0x111111,
+            map: map,
+            specularMap: specularMap,
+            normalMap: normalMap,
+            shininess: 25,
+        });
+
+        scene.add(mesh);
+        mesh.scale.set(10, 10, 10);
+    });
+}
+
+function shoot() {
+    position.copy(intersection.point);
+    orientation.copy(mouseHelper.rotation);
+
+    if (params.rotate) orientation.z = Math.random() * 2 * Math.PI;
+
+    const scale = params.minScale + Math.random() * (params.maxScale - params.minScale);
+    size.set(scale, scale, scale);
+
+    const material = decalMaterial.clone();
+    material.color.setHex(Math.random() * 0xffffff);
+
+    const m = new THREE.Mesh(new DecalGeometry(mesh, position, orientation, size), material);
+    m.renderOrder = decals.length; // give decals a fixed render order
+
+    decals.push(m);
+    scene.add(m);
+}
+
+function removeDecals() {
+    decals.forEach(function (d) {
+        scene.remove(d);
+    });
+
+    decals.length = 0;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_effects_anaglyph.ts b/examples-testing/examples/webgl_effects_anaglyph.ts
new file mode 100644
index 000000000..8415973df
--- /dev/null
+++ b/examples-testing/examples/webgl_effects_anaglyph.ts
@@ -0,0 +1,114 @@
+import * as THREE from 'three';
+
+import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';
+
+let container, camera, scene, renderer, effect;
+
+const spheres = [];
+
+let mouseX = 0;
+let mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+document.addEventListener('mousemove', onDocumentMouseMove);
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
+    camera.position.z = 3;
+
+    const path = 'textures/cube/pisa/';
+    const format = '.png';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const textureCube = new THREE.CubeTextureLoader().load(urls);
+
+    scene = new THREE.Scene();
+    scene.background = textureCube;
+
+    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = Math.random() * 10 - 5;
+        mesh.position.y = Math.random() * 10 - 5;
+        mesh.position.z = Math.random() * 10 - 5;
+
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
+
+        scene.add(mesh);
+
+        spheres.push(mesh);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    const width = window.innerWidth || 2;
+    const height = window.innerHeight || 2;
+
+    effect = new AnaglyphEffect(renderer);
+    effect.setSize(width, height);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    effect.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) / 100;
+    mouseY = (event.clientY - windowHalfY) / 100;
+}
+
+//
+
+function animate() {
+    render();
+}
+
+function render() {
+    const timer = 0.0001 * Date.now();
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0, il = spheres.length; i < il; i++) {
+        const sphere = spheres[i];
+
+        sphere.position.x = 5 * Math.cos(timer + i);
+        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
+    }
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_effects_ascii.ts b/examples-testing/examples/webgl_effects_ascii.ts
new file mode 100644
index 000000000..a412bb79e
--- /dev/null
+++ b/examples-testing/examples/webgl_effects_ascii.ts
@@ -0,0 +1,81 @@
+import * as THREE from 'three';
+
+import { AsciiEffect } from 'three/addons/effects/AsciiEffect.js';
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+
+let camera, controls, scene, renderer, effect;
+
+let sphere, plane;
+
+const start = Date.now();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.y = 150;
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0, 0, 0);
+
+    const pointLight1 = new THREE.PointLight(0xffffff, 3, 0, 0);
+    pointLight1.position.set(500, 500, 500);
+    scene.add(pointLight1);
+
+    const pointLight2 = new THREE.PointLight(0xffffff, 1, 0, 0);
+    pointLight2.position.set(-500, -500, -500);
+    scene.add(pointLight2);
+
+    sphere = new THREE.Mesh(new THREE.SphereGeometry(200, 20, 10), new THREE.MeshPhongMaterial({ flatShading: true }));
+    scene.add(sphere);
+
+    // Plane
+
+    plane = new THREE.Mesh(new THREE.PlaneGeometry(400, 400), new THREE.MeshBasicMaterial({ color: 0xe0e0e0 }));
+    plane.position.y = -200;
+    plane.rotation.x = -Math.PI / 2;
+    scene.add(plane);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    effect = new AsciiEffect(renderer, ' .:-+*=%@#', { invert: true });
+    effect.setSize(window.innerWidth, window.innerHeight);
+    effect.domElement.style.color = 'white';
+    effect.domElement.style.backgroundColor = 'black';
+
+    // Special case: append effect.domElement, instead of renderer.domElement.
+    // AsciiEffect creates a custom domElement (a div container) where the ASCII elements are placed.
+
+    document.body.appendChild(effect.domElement);
+
+    controls = new TrackballControls(camera, effect.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    effect.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const timer = Date.now() - start;
+
+    sphere.position.y = Math.abs(Math.sin(timer * 0.002)) * 150;
+    sphere.rotation.x = timer * 0.0003;
+    sphere.rotation.z = timer * 0.0002;
+
+    controls.update();
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_effects_parallaxbarrier.ts b/examples-testing/examples/webgl_effects_parallaxbarrier.ts
new file mode 100644
index 000000000..90c867973
--- /dev/null
+++ b/examples-testing/examples/webgl_effects_parallaxbarrier.ts
@@ -0,0 +1,110 @@
+import * as THREE from 'three';
+
+import { ParallaxBarrierEffect } from 'three/addons/effects/ParallaxBarrierEffect.js';
+
+let container, camera, scene, renderer, effect;
+
+const spheres = [];
+
+let mouseX = 0;
+let mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+document.addEventListener('mousemove', onDocumentMouseMove);
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
+    camera.position.z = 3;
+
+    const path = 'textures/cube/pisa/';
+    const format = '.png';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const textureCube = new THREE.CubeTextureLoader().load(urls);
+
+    scene = new THREE.Scene();
+    scene.background = textureCube;
+
+    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = Math.random() * 10 - 5;
+        mesh.position.y = Math.random() * 10 - 5;
+        mesh.position.z = Math.random() * 10 - 5;
+
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
+
+        scene.add(mesh);
+
+        spheres.push(mesh);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    const width = window.innerWidth || 2;
+    const height = window.innerHeight || 2;
+
+    effect = new ParallaxBarrierEffect(renderer);
+    effect.setSize(width, height);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    effect.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) / 100;
+    mouseY = (event.clientY - windowHalfY) / 100;
+}
+
+//
+
+function animate() {
+    const timer = 0.0001 * Date.now();
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0, il = spheres.length; i < il; i++) {
+        const sphere = spheres[i];
+
+        sphere.position.x = 5 * Math.cos(timer + i);
+        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
+    }
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_effects_peppersghost.ts b/examples-testing/examples/webgl_effects_peppersghost.ts
new file mode 100644
index 000000000..41dfb4b65
--- /dev/null
+++ b/examples-testing/examples/webgl_effects_peppersghost.ts
@@ -0,0 +1,85 @@
+import * as THREE from 'three';
+
+import { PeppersGhostEffect } from 'three/addons/effects/PeppersGhostEffect.js';
+
+let container;
+
+let camera, scene, renderer, effect;
+let group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
+
+    scene = new THREE.Scene();
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    // Cube
+
+    const geometry = new THREE.BoxGeometry().toNonIndexed(); // ensure unique vertices for each triangle
+
+    const position = geometry.attributes.position;
+    const colors = [];
+    const color = new THREE.Color();
+
+    // generate for each side of the cube a different color
+
+    for (let i = 0; i < position.count; i += 6) {
+        color.setHex(Math.random() * 0xffffff);
+
+        // first face
+
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+
+        // second face
+
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+        colors.push(color.r, color.g, color.b);
+    }
+
+    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    const material = new THREE.MeshBasicMaterial({ vertexColors: true });
+
+    for (let i = 0; i < 10; i++) {
+        const cube = new THREE.Mesh(geometry, material);
+        cube.position.x = Math.random() * 2 - 1;
+        cube.position.y = Math.random() * 2 - 1;
+        cube.position.z = Math.random() * 2 - 1;
+        cube.scale.multiplyScalar(Math.random() + 0.5);
+        group.add(cube);
+    }
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    effect = new PeppersGhostEffect(renderer);
+    effect.setSize(window.innerWidth, window.innerHeight);
+    effect.cameraDistance = 5;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    effect.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    group.rotation.y += 0.01;
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_effects_stereo.ts b/examples-testing/examples/webgl_effects_stereo.ts
new file mode 100644
index 000000000..dd2f61f91
--- /dev/null
+++ b/examples-testing/examples/webgl_effects_stereo.ts
@@ -0,0 +1,98 @@
+import * as THREE from 'three';
+
+import { StereoEffect } from 'three/addons/effects/StereoEffect.js';
+
+let container, camera, scene, renderer, effect;
+
+const spheres = [];
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+document.addEventListener('mousemove', onDocumentMouseMove);
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
+    camera.position.z = 3200;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.CubeTextureLoader()
+        .setPath('textures/cube/Park3Med/')
+        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+
+    const geometry = new THREE.SphereGeometry(100, 32, 16);
+
+    const textureCube = new THREE.CubeTextureLoader()
+        .setPath('textures/cube/Park3Med/')
+        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+    textureCube.mapping = THREE.CubeRefractionMapping;
+
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube, refractionRatio: 0.95 });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 10000 - 5000;
+        mesh.position.y = Math.random() * 10000 - 5000;
+        mesh.position.z = Math.random() * 10000 - 5000;
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
+        scene.add(mesh);
+
+        spheres.push(mesh);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    effect = new StereoEffect(renderer);
+    effect.setSize(window.innerWidth, window.innerHeight);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    effect.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) * 10;
+    mouseY = (event.clientY - windowHalfY) * 10;
+}
+
+//
+
+function animate() {
+    const timer = 0.0001 * Date.now();
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+    camera.lookAt(scene.position);
+
+    for (let i = 0, il = spheres.length; i < il; i++) {
+        const sphere = spheres[i];
+
+        sphere.position.x = 5000 * Math.cos(timer + i);
+        sphere.position.y = 5000 * Math.sin(timer + i * 1.1);
+    }
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_framebuffer_texture.ts b/examples-testing/examples/webgl_framebuffer_texture.ts
new file mode 100644
index 000000000..df4acc9d6
--- /dev/null
+++ b/examples-testing/examples/webgl_framebuffer_texture.ts
@@ -0,0 +1,151 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
+
+let camera, scene, renderer;
+let line, sprite, texture;
+
+let cameraOrtho, sceneOrtho;
+
+let offset = 0;
+
+const dpr = window.devicePixelRatio;
+
+const textureSize = 128 * dpr;
+const vector = new THREE.Vector2();
+const color = new THREE.Color();
+
+init();
+
+function init() {
+    //
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000);
+    camera.position.z = 20;
+
+    cameraOrtho = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 1, 10);
+    cameraOrtho.position.z = 10;
+
+    scene = new THREE.Scene();
+    sceneOrtho = new THREE.Scene();
+
+    //
+
+    const points = GeometryUtils.gosper(8);
+
+    const geometry = new THREE.BufferGeometry();
+    const positionAttribute = new THREE.Float32BufferAttribute(points, 3);
+    geometry.setAttribute('position', positionAttribute);
+    geometry.center();
+
+    const colorAttribute = new THREE.BufferAttribute(new Float32Array(positionAttribute.array.length), 3);
+    colorAttribute.setUsage(THREE.DynamicDrawUsage);
+    geometry.setAttribute('color', colorAttribute);
+
+    const material = new THREE.LineBasicMaterial({ vertexColors: true });
+
+    line = new THREE.Line(geometry, material);
+    line.scale.setScalar(0.05);
+    scene.add(line);
+
+    //
+
+    texture = new THREE.FramebufferTexture(textureSize, textureSize);
+
+    //
+
+    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
+    sprite = new THREE.Sprite(spriteMaterial);
+    sprite.scale.set(textureSize, textureSize, 1);
+    sceneOrtho.add(sprite);
+
+    updateSpritePosition();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const selection = document.getElementById('selection');
+    const controls = new OrbitControls(camera, selection);
+    controls.enablePan = false;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    cameraOrtho.left = -width / 2;
+    cameraOrtho.right = width / 2;
+    cameraOrtho.top = height / 2;
+    cameraOrtho.bottom = -height / 2;
+    cameraOrtho.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    updateSpritePosition();
+}
+
+function updateSpritePosition() {
+    const halfWidth = window.innerWidth / 2;
+    const halfHeight = window.innerHeight / 2;
+
+    const halfImageWidth = textureSize / 2;
+    const halfImageHeight = textureSize / 2;
+
+    sprite.position.set(-halfWidth + halfImageWidth, halfHeight - halfImageHeight, 1);
+}
+
+function animate() {
+    const colorAttribute = line.geometry.getAttribute('color');
+    updateColors(colorAttribute);
+
+    // scene rendering
+
+    renderer.clear();
+    renderer.render(scene, camera);
+
+    // calculate start position for copying data
+
+    vector.x = (window.innerWidth * dpr) / 2 - textureSize / 2;
+    vector.y = (window.innerHeight * dpr) / 2 - textureSize / 2;
+
+    renderer.copyFramebufferToTexture(texture, vector);
+
+    renderer.clearDepth();
+    renderer.render(sceneOrtho, cameraOrtho);
+}
+
+function updateColors(colorAttribute) {
+    const l = colorAttribute.count;
+
+    for (let i = 0; i < l; i++) {
+        const h = ((offset + i) % l) / l;
+
+        color.setHSL(h, 1, 0.5);
+        colorAttribute.setX(i, color.r);
+        colorAttribute.setY(i, color.g);
+        colorAttribute.setZ(i, color.b);
+    }
+
+    colorAttribute.needsUpdate = true;
+
+    offset -= 25;
+}
diff --git a/examples-testing/examples/webgl_furnace_test.ts b/examples-testing/examples/webgl_furnace_test.ts
new file mode 100644
index 000000000..a81954176
--- /dev/null
+++ b/examples-testing/examples/webgl_furnace_test.ts
@@ -0,0 +1,96 @@
+import * as THREE from 'three';
+
+let scene, camera, renderer, radianceMap;
+
+const COLOR = 0xcccccc;
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const aspect = width / height;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(width, height);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    document.body.addEventListener('mouseover', function () {
+        scene.traverse(function (child) {
+            if (child.isMesh) child.material.color.setHex(0xffffff);
+        });
+
+        render();
+    });
+
+    document.body.addEventListener('mouseout', function () {
+        scene.traverse(function (child) {
+            if (child.isMesh) child.material.color.setHex(0xccccff); // tinted for visibility
+        });
+
+        render();
+    });
+
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(40, aspect, 1, 30);
+    camera.position.set(0, 0, 18);
+}
+
+function createObjects() {
+    const geometry = new THREE.SphereGeometry(0.4, 32, 16);
+
+    for (let x = 0; x <= 10; x++) {
+        for (let y = 0; y <= 10; y++) {
+            const material = new THREE.MeshPhysicalMaterial({
+                roughness: x / 10,
+                metalness: y / 10,
+                color: 0xffffff,
+                envMap: radianceMap,
+                envMapIntensity: 1,
+                transmission: 0,
+                ior: 1.5,
+            });
+
+            const mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = x - 5;
+            mesh.position.y = 5 - y;
+            scene.add(mesh);
+        }
+    }
+}
+
+function createEnvironment() {
+    const envScene = new THREE.Scene();
+    envScene.background = new THREE.Color(COLOR);
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    radianceMap = pmremGenerator.fromScene(envScene).texture;
+    pmremGenerator.dispose();
+
+    scene.background = envScene.background;
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+Promise.resolve().then(init).then(createEnvironment).then(createObjects).then(render);
diff --git a/examples-testing/examples/webgl_geometries.ts b/examples-testing/examples/webgl_geometries.ts
new file mode 100644
index 000000000..2b2d02613
--- /dev/null
+++ b/examples-testing/examples/webgl_geometries.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.y = 400;
+
+    scene = new THREE.Scene();
+
+    let object;
+
+    const ambientLight = new THREE.AmbientLight(0xcccccc, 1.5);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 2.5, 0, 0);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    map.wrapS = map.wrapT = THREE.RepeatWrapping;
+    map.anisotropy = 16;
+    map.colorSpace = THREE.SRGBColorSpace;
+
+    const material = new THREE.MeshPhongMaterial({ map: map, side: THREE.DoubleSide });
+
+    //
+
+    object = new THREE.Mesh(new THREE.SphereGeometry(75, 20, 10), material);
+    object.position.set(-300, 0, 200);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.IcosahedronGeometry(75, 1), material);
+    object.position.set(-100, 0, 200);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.OctahedronGeometry(75, 2), material);
+    object.position.set(100, 0, 200);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.TetrahedronGeometry(75, 0), material);
+    object.position.set(300, 0, 200);
+    scene.add(object);
+
+    //
+
+    object = new THREE.Mesh(new THREE.PlaneGeometry(100, 100, 4, 4), material);
+    object.position.set(-300, 0, 0);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100, 4, 4, 4), material);
+    object.position.set(-100, 0, 0);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.CircleGeometry(50, 20, 0, Math.PI * 2), material);
+    object.position.set(100, 0, 0);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.RingGeometry(10, 50, 20, 5, 0, Math.PI * 2), material);
+    object.position.set(300, 0, 0);
+    scene.add(object);
+
+    //
+
+    object = new THREE.Mesh(new THREE.CylinderGeometry(25, 75, 100, 40, 5), material);
+    object.position.set(-300, 0, -200);
+    scene.add(object);
+
+    const points = [];
+
+    for (let i = 0; i < 50; i++) {
+        points.push(new THREE.Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
+    }
+
+    object = new THREE.Mesh(new THREE.LatheGeometry(points, 20), material);
+    object.position.set(-100, 0, -200);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.TorusGeometry(50, 20, 20, 20), material);
+    object.position.set(100, 0, -200);
+    scene.add(object);
+
+    object = new THREE.Mesh(new THREE.TorusKnotGeometry(50, 10, 50, 20), material);
+    object.position.set(300, 0, -200);
+    scene.add(object);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const timer = Date.now() * 0.0001;
+
+    camera.position.x = Math.cos(timer) * 800;
+    camera.position.z = Math.sin(timer) * 800;
+
+    camera.lookAt(scene.position);
+
+    scene.traverse(function (object) {
+        if (object.isMesh === true) {
+            object.rotation.x = timer * 5;
+            object.rotation.y = timer * 2.5;
+        }
+    });
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometries_parametric.ts b/examples-testing/examples/webgl_geometries_parametric.ts
new file mode 100644
index 000000000..29bf7ae26
--- /dev/null
+++ b/examples-testing/examples/webgl_geometries_parametric.ts
@@ -0,0 +1,124 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import * as Curves from 'three/addons/curves/CurveExtras.js';
+import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
+import { ParametricGeometries } from 'three/addons/geometries/ParametricGeometries.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.y = 400;
+
+    scene = new THREE.Scene();
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xcccccc, 1.5);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 2.5, 0, 0);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    //
+
+    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    map.wrapS = map.wrapT = THREE.RepeatWrapping;
+    map.anisotropy = 16;
+    map.colorSpace = THREE.SRGBColorSpace;
+
+    const material = new THREE.MeshPhongMaterial({ map: map, side: THREE.DoubleSide });
+
+    //
+
+    let geometry, object;
+
+    geometry = new ParametricGeometry(ParametricGeometries.plane(100, 100), 10, 10);
+    geometry.center();
+    object = new THREE.Mesh(geometry, material);
+    object.position.set(-200, 0, 200);
+    scene.add(object);
+
+    geometry = new ParametricGeometry(ParametricGeometries.klein, 20, 20);
+    object = new THREE.Mesh(geometry, material);
+    object.position.set(0, 0, 200);
+    object.scale.multiplyScalar(5);
+    scene.add(object);
+
+    geometry = new ParametricGeometry(ParametricGeometries.mobius, 20, 20);
+    object = new THREE.Mesh(geometry, material);
+    object.position.set(200, 0, 200);
+    object.scale.multiplyScalar(30);
+    scene.add(object);
+
+    //
+
+    const GrannyKnot = new Curves.GrannyKnot();
+
+    const torus = new ParametricGeometries.TorusKnotGeometry(50, 10, 50, 20, 2, 3);
+    const sphere = new ParametricGeometries.SphereGeometry(50, 20, 10);
+    const tube = new ParametricGeometries.TubeGeometry(GrannyKnot, 100, 3, 8, true);
+
+    object = new THREE.Mesh(torus, material);
+    object.position.set(-200, 0, -200);
+    scene.add(object);
+
+    object = new THREE.Mesh(sphere, material);
+    object.position.set(0, 0, -200);
+    scene.add(object);
+
+    object = new THREE.Mesh(tube, material);
+    object.position.set(200, 0, -200);
+    object.scale.multiplyScalar(2);
+    scene.add(object);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const timer = Date.now() * 0.0001;
+
+    camera.position.x = Math.cos(timer) * 800;
+    camera.position.z = Math.sin(timer) * 800;
+
+    camera.lookAt(scene.position);
+
+    scene.traverse(function (object) {
+        if (object.isMesh === true) {
+            object.rotation.x = timer * 5;
+            object.rotation.y = timer * 2.5;
+        }
+    });
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_colors.ts b/examples-testing/examples/webgl_geometry_colors.ts
new file mode 100644
index 000000000..bc0bf5174
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_colors.ts
@@ -0,0 +1,176 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 1800;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 0, 1);
+    scene.add(light);
+
+    // shadow
+
+    const canvas = document.createElement('canvas');
+    canvas.width = 128;
+    canvas.height = 128;
+
+    const context = canvas.getContext('2d');
+    const gradient = context.createRadialGradient(
+        canvas.width / 2,
+        canvas.height / 2,
+        0,
+        canvas.width / 2,
+        canvas.height / 2,
+        canvas.width / 2,
+    );
+    gradient.addColorStop(0.1, 'rgba(210,210,210,1)');
+    gradient.addColorStop(1, 'rgba(255,255,255,1)');
+
+    context.fillStyle = gradient;
+    context.fillRect(0, 0, canvas.width, canvas.height);
+
+    const shadowTexture = new THREE.CanvasTexture(canvas);
+
+    const shadowMaterial = new THREE.MeshBasicMaterial({ map: shadowTexture });
+    const shadowGeo = new THREE.PlaneGeometry(300, 300, 1, 1);
+
+    let shadowMesh;
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.y = -250;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.y = -250;
+    shadowMesh.position.x = -400;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.y = -250;
+    shadowMesh.position.x = 400;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    const radius = 200;
+
+    const geometry1 = new THREE.IcosahedronGeometry(radius, 1);
+
+    const count = geometry1.attributes.position.count;
+    geometry1.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
+
+    const geometry2 = geometry1.clone();
+    const geometry3 = geometry1.clone();
+
+    const color = new THREE.Color();
+    const positions1 = geometry1.attributes.position;
+    const positions2 = geometry2.attributes.position;
+    const positions3 = geometry3.attributes.position;
+    const colors1 = geometry1.attributes.color;
+    const colors2 = geometry2.attributes.color;
+    const colors3 = geometry3.attributes.color;
+
+    for (let i = 0; i < count; i++) {
+        color.setHSL((positions1.getY(i) / radius + 1) / 2, 1.0, 0.5, THREE.SRGBColorSpace);
+        colors1.setXYZ(i, color.r, color.g, color.b);
+
+        color.setHSL(0, (positions2.getY(i) / radius + 1) / 2, 0.5, THREE.SRGBColorSpace);
+        colors2.setXYZ(i, color.r, color.g, color.b);
+
+        color.setRGB(1, 0.8 - (positions3.getY(i) / radius + 1) / 2, 0, THREE.SRGBColorSpace);
+        colors3.setXYZ(i, color.r, color.g, color.b);
+    }
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xffffff,
+        flatShading: true,
+        vertexColors: true,
+        shininess: 0,
+    });
+
+    const wireframeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, transparent: true });
+
+    let mesh = new THREE.Mesh(geometry1, material);
+    let wireframe = new THREE.Mesh(geometry1, wireframeMaterial);
+    mesh.add(wireframe);
+    mesh.position.x = -400;
+    mesh.rotation.x = -1.87;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry2, material);
+    wireframe = new THREE.Mesh(geometry2, wireframeMaterial);
+    mesh.add(wireframe);
+    mesh.position.x = 400;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry3, material);
+    wireframe = new THREE.Mesh(geometry3, wireframeMaterial);
+    mesh.add(wireframe);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_colors_lookuptable.ts b/examples-testing/examples/webgl_geometry_colors_lookuptable.ts
new file mode 100644
index 000000000..6b0138529
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_colors_lookuptable.ts
@@ -0,0 +1,148 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Lut } from 'three/addons/math/Lut.js';
+
+let container;
+
+let perpCamera, orthoCamera, renderer, lut;
+
+let mesh, sprite;
+let scene, uiScene;
+
+let params;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    uiScene = new THREE.Scene();
+
+    lut = new Lut();
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    perpCamera = new THREE.PerspectiveCamera(60, width / height, 1, 100);
+    perpCamera.position.set(0, 0, 10);
+    scene.add(perpCamera);
+
+    orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 2);
+    orthoCamera.position.set(0.5, 0, 1);
+
+    sprite = new THREE.Sprite(
+        new THREE.SpriteMaterial({
+            map: new THREE.CanvasTexture(lut.createCanvas()),
+        }),
+    );
+    sprite.material.map.colorSpace = THREE.SRGBColorSpace;
+    sprite.scale.x = 0.125;
+    uiScene.add(sprite);
+
+    mesh = new THREE.Mesh(
+        undefined,
+        new THREE.MeshLambertMaterial({
+            side: THREE.DoubleSide,
+            color: 0xf5f5f5,
+            vertexColors: true,
+        }),
+    );
+    scene.add(mesh);
+
+    params = {
+        colorMap: 'rainbow',
+    };
+    loadModel();
+
+    const pointLight = new THREE.PointLight(0xffffff, 3, 0, 0);
+    perpCamera.add(pointLight);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.autoClear = false;
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const controls = new OrbitControls(perpCamera, renderer.domElement);
+    controls.addEventListener('change', render);
+
+    const gui = new GUI();
+
+    gui.add(params, 'colorMap', ['rainbow', 'cooltowarm', 'blackbody', 'grayscale']).onChange(function () {
+        updateColors();
+        render();
+    });
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    perpCamera.aspect = width / height;
+    perpCamera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    render();
+}
+
+function render() {
+    renderer.clear();
+    renderer.render(scene, perpCamera);
+    renderer.render(uiScene, orthoCamera);
+}
+
+function loadModel() {
+    const loader = new THREE.BufferGeometryLoader();
+    loader.load('models/json/pressure.json', function (geometry) {
+        geometry.center();
+        geometry.computeVertexNormals();
+
+        // default color attribute
+        const colors = [];
+
+        for (let i = 0, n = geometry.attributes.position.count; i < n; ++i) {
+            colors.push(1, 1, 1);
+        }
+
+        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+        mesh.geometry = geometry;
+        updateColors();
+
+        render();
+    });
+}
+
+function updateColors() {
+    lut.setColorMap(params.colorMap);
+
+    lut.setMax(2000);
+    lut.setMin(0);
+
+    const geometry = mesh.geometry;
+    const pressures = geometry.attributes.pressure;
+    const colors = geometry.attributes.color;
+    const color = new THREE.Color();
+
+    for (let i = 0; i < pressures.array.length; i++) {
+        const colorValue = pressures.array[i];
+
+        color.copy(lut.getColor(colorValue)).convertSRGBToLinear();
+
+        colors.setXYZ(i, color.r, color.g, color.b);
+    }
+
+    colors.needsUpdate = true;
+
+    const map = sprite.material.map;
+    lut.updateCanvas(map.image);
+    map.needsUpdate = true;
+}
diff --git a/examples-testing/examples/webgl_geometry_convex.ts b/examples-testing/examples/webgl_geometry_convex.ts
new file mode 100644
index 000000000..ade9cb801
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_convex.ts
@@ -0,0 +1,117 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js';
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let group, camera, scene, renderer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(15, 20, 30);
+    scene.add(camera);
+
+    // controls
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 20;
+    controls.maxDistance = 50;
+    controls.maxPolarAngle = Math.PI / 2;
+
+    // ambient light
+
+    scene.add(new THREE.AmbientLight(0x666666));
+
+    // point light
+
+    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
+    camera.add(light);
+
+    // helper
+
+    scene.add(new THREE.AxesHelper(20));
+
+    // textures
+
+    const loader = new THREE.TextureLoader();
+    const texture = loader.load('textures/sprites/disc.png');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    // points
+
+    let dodecahedronGeometry = new THREE.DodecahedronGeometry(10);
+
+    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
+
+    dodecahedronGeometry.deleteAttribute('normal');
+    dodecahedronGeometry.deleteAttribute('uv');
+
+    dodecahedronGeometry = BufferGeometryUtils.mergeVertices(dodecahedronGeometry);
+
+    const vertices = [];
+    const positionAttribute = dodecahedronGeometry.getAttribute('position');
+
+    for (let i = 0; i < positionAttribute.count; i++) {
+        const vertex = new THREE.Vector3();
+        vertex.fromBufferAttribute(positionAttribute, i);
+        vertices.push(vertex);
+    }
+
+    const pointsMaterial = new THREE.PointsMaterial({
+        color: 0x0080ff,
+        map: texture,
+        size: 1,
+        alphaTest: 0.5,
+    });
+
+    const pointsGeometry = new THREE.BufferGeometry().setFromPoints(vertices);
+
+    const points = new THREE.Points(pointsGeometry, pointsMaterial);
+    group.add(points);
+
+    // convex hull
+
+    const meshMaterial = new THREE.MeshLambertMaterial({
+        color: 0xffffff,
+        opacity: 0.5,
+        side: THREE.DoubleSide,
+        transparent: true,
+    });
+
+    const meshGeometry = new ConvexGeometry(vertices);
+
+    const mesh = new THREE.Mesh(meshGeometry, meshMaterial);
+    group.add(mesh);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    group.rotation.y += 0.005;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_cube.ts b/examples-testing/examples/webgl_geometry_cube.ts
new file mode 100644
index 000000000..572601acb
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_cube.ts
@@ -0,0 +1,46 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let mesh;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+
+    const texture = new THREE.TextureLoader().load('textures/crate.gif');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const geometry = new THREE.BoxGeometry();
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.rotation.x += 0.005;
+    mesh.rotation.y += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_dynamic.ts b/examples-testing/examples/webgl_geometry_dynamic.ts
new file mode 100644
index 000000000..06e858f54
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_dynamic.ts
@@ -0,0 +1,97 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+
+let camera, controls, scene, renderer, stats;
+
+let mesh, geometry, material, clock;
+
+const worldWidth = 128,
+    worldDepth = 128;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
+    camera.position.y = 200;
+
+    clock = new THREE.Clock();
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xaaccff);
+    scene.fog = new THREE.FogExp2(0xaaccff, 0.0007);
+
+    geometry = new THREE.PlaneGeometry(20000, 20000, worldWidth - 1, worldDepth - 1);
+    geometry.rotateX(-Math.PI / 2);
+
+    const position = geometry.attributes.position;
+    position.usage = THREE.DynamicDrawUsage;
+
+    for (let i = 0; i < position.count; i++) {
+        const y = 35 * Math.sin(i / 2);
+        position.setY(i, y);
+    }
+
+    const texture = new THREE.TextureLoader().load('textures/water.jpg');
+    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+    texture.repeat.set(5, 5);
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    material = new THREE.MeshBasicMaterial({ color: 0x0044ff, map: texture });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+
+    controls.movementSpeed = 500;
+    controls.lookSpeed = 0.1;
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+    const time = clock.getElapsedTime() * 10;
+
+    const position = geometry.attributes.position;
+
+    for (let i = 0; i < position.count; i++) {
+        const y = 35 * Math.sin(i / 5 + (time + i) / 7);
+        position.setY(i, y);
+    }
+
+    position.needsUpdate = true;
+
+    controls.update(delta);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_extrude_shapes.ts b/examples-testing/examples/webgl_geometry_extrude_shapes.ts
new file mode 100644
index 000000000..7428aee31
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_extrude_shapes.ts
@@ -0,0 +1,149 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+
+let camera, scene, renderer, controls;
+
+init();
+
+function init() {
+    const info = document.createElement('div');
+    info.style.position = 'absolute';
+    info.style.top = '10px';
+    info.style.width = '100%';
+    info.style.textAlign = 'center';
+    info.style.color = '#fff';
+    info.style.link = '#f80';
+    info.innerHTML =
+        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - geometry extrude shapes';
+    document.body.appendChild(info);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x222222);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 500);
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.minDistance = 200;
+    controls.maxDistance = 500;
+
+    scene.add(new THREE.AmbientLight(0x666666));
+
+    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
+    light.position.copy(camera.position);
+    scene.add(light);
+
+    //
+
+    const closedSpline = new THREE.CatmullRomCurve3([
+        new THREE.Vector3(-60, -100, 60),
+        new THREE.Vector3(-60, 20, 60),
+        new THREE.Vector3(-60, 120, 60),
+        new THREE.Vector3(60, 20, -60),
+        new THREE.Vector3(60, -100, -60),
+    ]);
+
+    closedSpline.curveType = 'catmullrom';
+    closedSpline.closed = true;
+
+    const extrudeSettings1 = {
+        steps: 100,
+        bevelEnabled: false,
+        extrudePath: closedSpline,
+    };
+
+    const pts1 = [],
+        count = 3;
+
+    for (let i = 0; i < count; i++) {
+        const l = 20;
+
+        const a = ((2 * i) / count) * Math.PI;
+
+        pts1.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
+    }
+
+    const shape1 = new THREE.Shape(pts1);
+
+    const geometry1 = new THREE.ExtrudeGeometry(shape1, extrudeSettings1);
+
+    const material1 = new THREE.MeshLambertMaterial({ color: 0xb00000, wireframe: false });
+
+    const mesh1 = new THREE.Mesh(geometry1, material1);
+
+    scene.add(mesh1);
+
+    //
+
+    const randomPoints = [];
+
+    for (let i = 0; i < 10; i++) {
+        randomPoints.push(
+            new THREE.Vector3((i - 4.5) * 50, THREE.MathUtils.randFloat(-50, 50), THREE.MathUtils.randFloat(-50, 50)),
+        );
+    }
+
+    const randomSpline = new THREE.CatmullRomCurve3(randomPoints);
+
+    //
+
+    const extrudeSettings2 = {
+        steps: 200,
+        bevelEnabled: false,
+        extrudePath: randomSpline,
+    };
+
+    const pts2 = [],
+        numPts = 5;
+
+    for (let i = 0; i < numPts * 2; i++) {
+        const l = i % 2 == 1 ? 10 : 20;
+
+        const a = (i / numPts) * Math.PI;
+
+        pts2.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
+    }
+
+    const shape2 = new THREE.Shape(pts2);
+
+    const geometry2 = new THREE.ExtrudeGeometry(shape2, extrudeSettings2);
+
+    const material2 = new THREE.MeshLambertMaterial({ color: 0xff8000, wireframe: false });
+
+    const mesh2 = new THREE.Mesh(geometry2, material2);
+
+    scene.add(mesh2);
+
+    //
+
+    const materials = [material1, material2];
+
+    const extrudeSettings3 = {
+        depth: 20,
+        steps: 1,
+        bevelEnabled: true,
+        bevelThickness: 2,
+        bevelSize: 4,
+        bevelSegments: 1,
+    };
+
+    const geometry3 = new THREE.ExtrudeGeometry(shape2, extrudeSettings3);
+
+    const mesh3 = new THREE.Mesh(geometry3, materials);
+
+    mesh3.position.set(50, 100, 50);
+
+    scene.add(mesh3);
+}
+
+function animate() {
+    controls.update();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_extrude_splines.ts b/examples-testing/examples/webgl_geometry_extrude_splines.ts
new file mode 100644
index 000000000..0741083da
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_extrude_splines.ts
@@ -0,0 +1,310 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import * as Curves from 'three/addons/curves/CurveExtras.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, stats;
+
+let camera, scene, renderer, splineCamera, cameraHelper, cameraEye;
+
+const direction = new THREE.Vector3();
+const binormal = new THREE.Vector3();
+const normal = new THREE.Vector3();
+const position = new THREE.Vector3();
+const lookAt = new THREE.Vector3();
+
+const pipeSpline = new THREE.CatmullRomCurve3([
+    new THREE.Vector3(0, 10, -10),
+    new THREE.Vector3(10, 0, -10),
+    new THREE.Vector3(20, 0, 0),
+    new THREE.Vector3(30, 0, 10),
+    new THREE.Vector3(30, 0, 20),
+    new THREE.Vector3(20, 0, 30),
+    new THREE.Vector3(10, 0, 30),
+    new THREE.Vector3(0, 0, 30),
+    new THREE.Vector3(-10, 10, 30),
+    new THREE.Vector3(-10, 20, 30),
+    new THREE.Vector3(0, 30, 30),
+    new THREE.Vector3(10, 30, 30),
+    new THREE.Vector3(20, 30, 15),
+    new THREE.Vector3(10, 30, 10),
+    new THREE.Vector3(0, 30, 10),
+    new THREE.Vector3(-10, 20, 10),
+    new THREE.Vector3(-10, 10, 10),
+    new THREE.Vector3(0, 0, 10),
+    new THREE.Vector3(10, -10, 10),
+    new THREE.Vector3(20, -15, 10),
+    new THREE.Vector3(30, -15, 10),
+    new THREE.Vector3(40, -15, 10),
+    new THREE.Vector3(50, -15, 10),
+    new THREE.Vector3(60, 0, 10),
+    new THREE.Vector3(70, 0, 0),
+    new THREE.Vector3(80, 0, 0),
+    new THREE.Vector3(90, 0, 0),
+    new THREE.Vector3(100, 0, 0),
+]);
+
+const sampleClosedSpline = new THREE.CatmullRomCurve3([
+    new THREE.Vector3(0, -40, -40),
+    new THREE.Vector3(0, 40, -40),
+    new THREE.Vector3(0, 140, -40),
+    new THREE.Vector3(0, 40, 40),
+    new THREE.Vector3(0, -40, 40),
+]);
+
+sampleClosedSpline.curveType = 'catmullrom';
+sampleClosedSpline.closed = true;
+
+// Keep a dictionary of Curve instances
+const splines = {
+    GrannyKnot: new Curves.GrannyKnot(),
+    HeartCurve: new Curves.HeartCurve(3.5),
+    VivianiCurve: new Curves.VivianiCurve(70),
+    KnotCurve: new Curves.KnotCurve(),
+    HelixCurve: new Curves.HelixCurve(),
+    TrefoilKnot: new Curves.TrefoilKnot(),
+    TorusKnot: new Curves.TorusKnot(20),
+    CinquefoilKnot: new Curves.CinquefoilKnot(20),
+    TrefoilPolynomialKnot: new Curves.TrefoilPolynomialKnot(14),
+    FigureEightPolynomialKnot: new Curves.FigureEightPolynomialKnot(),
+    DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
+    DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
+    DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
+    DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
+    PipeSpline: pipeSpline,
+    SampleClosedSpline: sampleClosedSpline,
+};
+
+let parent, tubeGeometry, mesh;
+
+const params = {
+    spline: 'GrannyKnot',
+    scale: 4,
+    extrusionSegments: 100,
+    radiusSegments: 3,
+    closed: true,
+    animationView: false,
+    lookAhead: false,
+    cameraHelper: false,
+};
+
+const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
+
+const wireframeMaterial = new THREE.MeshBasicMaterial({
+    color: 0x000000,
+    opacity: 0.3,
+    wireframe: true,
+    transparent: true,
+});
+
+function addTube() {
+    if (mesh !== undefined) {
+        parent.remove(mesh);
+        mesh.geometry.dispose();
+    }
+
+    const extrudePath = splines[params.spline];
+
+    tubeGeometry = new THREE.TubeGeometry(
+        extrudePath,
+        params.extrusionSegments,
+        2,
+        params.radiusSegments,
+        params.closed,
+    );
+
+    addGeometry(tubeGeometry);
+
+    setScale();
+}
+
+function setScale() {
+    mesh.scale.set(params.scale, params.scale, params.scale);
+}
+
+function addGeometry(geometry) {
+    // 3D shape
+
+    mesh = new THREE.Mesh(geometry, material);
+    const wireframe = new THREE.Mesh(geometry, wireframeMaterial);
+    mesh.add(wireframe);
+
+    parent.add(mesh);
+}
+
+function animateCamera() {
+    cameraHelper.visible = params.cameraHelper;
+    cameraEye.visible = params.cameraHelper;
+}
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
+    camera.position.set(0, 50, 500);
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    // light
+
+    scene.add(new THREE.AmbientLight(0xffffff));
+
+    const light = new THREE.DirectionalLight(0xffffff, 1.5);
+    light.position.set(0, 0, 1);
+    scene.add(light);
+
+    // tube
+
+    parent = new THREE.Object3D();
+    scene.add(parent);
+
+    splineCamera = new THREE.PerspectiveCamera(84, window.innerWidth / window.innerHeight, 0.01, 1000);
+    parent.add(splineCamera);
+
+    cameraHelper = new THREE.CameraHelper(splineCamera);
+    scene.add(cameraHelper);
+
+    addTube();
+
+    // debug camera
+
+    cameraEye = new THREE.Mesh(new THREE.SphereGeometry(5), new THREE.MeshBasicMaterial({ color: 0xdddddd }));
+    parent.add(cameraEye);
+
+    cameraHelper.visible = params.cameraHelper;
+    cameraEye.visible = params.cameraHelper;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // stats
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // dat.GUI
+
+    const gui = new GUI({ width: 285 });
+
+    const folderGeometry = gui.addFolder('Geometry');
+    folderGeometry.add(params, 'spline', Object.keys(splines)).onChange(function () {
+        addTube();
+    });
+    folderGeometry
+        .add(params, 'scale', 2, 10)
+        .step(2)
+        .onChange(function () {
+            setScale();
+        });
+    folderGeometry
+        .add(params, 'extrusionSegments', 50, 500)
+        .step(50)
+        .onChange(function () {
+            addTube();
+        });
+    folderGeometry
+        .add(params, 'radiusSegments', 2, 12)
+        .step(1)
+        .onChange(function () {
+            addTube();
+        });
+    folderGeometry.add(params, 'closed').onChange(function () {
+        addTube();
+    });
+    folderGeometry.open();
+
+    const folderCamera = gui.addFolder('Camera');
+    folderCamera.add(params, 'animationView').onChange(function () {
+        animateCamera();
+    });
+    folderCamera.add(params, 'lookAhead').onChange(function () {
+        animateCamera();
+    });
+    folderCamera.add(params, 'cameraHelper').onChange(function () {
+        animateCamera();
+    });
+    folderCamera.open();
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 100;
+    controls.maxDistance = 2000;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    // animate camera along spline
+
+    const time = Date.now();
+    const looptime = 20 * 1000;
+    const t = (time % looptime) / looptime;
+
+    tubeGeometry.parameters.path.getPointAt(t, position);
+    position.multiplyScalar(params.scale);
+
+    // interpolation
+
+    const segments = tubeGeometry.tangents.length;
+    const pickt = t * segments;
+    const pick = Math.floor(pickt);
+    const pickNext = (pick + 1) % segments;
+
+    binormal.subVectors(tubeGeometry.binormals[pickNext], tubeGeometry.binormals[pick]);
+    binormal.multiplyScalar(pickt - pick).add(tubeGeometry.binormals[pick]);
+
+    tubeGeometry.parameters.path.getTangentAt(t, direction);
+    const offset = 15;
+
+    normal.copy(binormal).cross(direction);
+
+    // we move on a offset on its binormal
+
+    position.add(normal.clone().multiplyScalar(offset));
+
+    splineCamera.position.copy(position);
+    cameraEye.position.copy(position);
+
+    // using arclength for stablization in look ahead
+
+    tubeGeometry.parameters.path.getPointAt((t + 30 / tubeGeometry.parameters.path.getLength()) % 1, lookAt);
+    lookAt.multiplyScalar(params.scale);
+
+    // camera orientation 2 - up orientation via normal
+
+    if (!params.lookAhead) lookAt.copy(position).add(direction);
+    splineCamera.matrix.lookAt(splineCamera.position, lookAt, normal);
+    splineCamera.quaternion.setFromRotationMatrix(splineCamera.matrix);
+
+    cameraHelper.update();
+
+    renderer.render(scene, params.animationView === true ? splineCamera : camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_minecraft.ts b/examples-testing/examples/webgl_geometry_minecraft.ts
new file mode 100644
index 000000000..765aa1e49
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_minecraft.ts
@@ -0,0 +1,183 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let container, stats;
+
+let camera, controls, scene, renderer;
+
+const worldWidth = 128,
+    worldDepth = 128;
+const worldHalfWidth = worldWidth / 2;
+const worldHalfDepth = worldDepth / 2;
+const data = generateHeight(worldWidth, worldDepth);
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
+    camera.position.y = getY(worldHalfWidth, worldHalfDepth) * 100 + 100;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xbfd1e5);
+
+    // sides
+
+    const matrix = new THREE.Matrix4();
+
+    const pxGeometry = new THREE.PlaneGeometry(100, 100);
+    pxGeometry.attributes.uv.array[1] = 0.5;
+    pxGeometry.attributes.uv.array[3] = 0.5;
+    pxGeometry.rotateY(Math.PI / 2);
+    pxGeometry.translate(50, 0, 0);
+
+    const nxGeometry = new THREE.PlaneGeometry(100, 100);
+    nxGeometry.attributes.uv.array[1] = 0.5;
+    nxGeometry.attributes.uv.array[3] = 0.5;
+    nxGeometry.rotateY(-Math.PI / 2);
+    nxGeometry.translate(-50, 0, 0);
+
+    const pyGeometry = new THREE.PlaneGeometry(100, 100);
+    pyGeometry.attributes.uv.array[5] = 0.5;
+    pyGeometry.attributes.uv.array[7] = 0.5;
+    pyGeometry.rotateX(-Math.PI / 2);
+    pyGeometry.translate(0, 50, 0);
+
+    const pzGeometry = new THREE.PlaneGeometry(100, 100);
+    pzGeometry.attributes.uv.array[1] = 0.5;
+    pzGeometry.attributes.uv.array[3] = 0.5;
+    pzGeometry.translate(0, 0, 50);
+
+    const nzGeometry = new THREE.PlaneGeometry(100, 100);
+    nzGeometry.attributes.uv.array[1] = 0.5;
+    nzGeometry.attributes.uv.array[3] = 0.5;
+    nzGeometry.rotateY(Math.PI);
+    nzGeometry.translate(0, 0, -50);
+
+    //
+
+    const geometries = [];
+
+    for (let z = 0; z < worldDepth; z++) {
+        for (let x = 0; x < worldWidth; x++) {
+            const h = getY(x, z);
+
+            matrix.makeTranslation(x * 100 - worldHalfWidth * 100, h * 100, z * 100 - worldHalfDepth * 100);
+
+            const px = getY(x + 1, z);
+            const nx = getY(x - 1, z);
+            const pz = getY(x, z + 1);
+            const nz = getY(x, z - 1);
+
+            geometries.push(pyGeometry.clone().applyMatrix4(matrix));
+
+            if ((px !== h && px !== h + 1) || x === 0) {
+                geometries.push(pxGeometry.clone().applyMatrix4(matrix));
+            }
+
+            if ((nx !== h && nx !== h + 1) || x === worldWidth - 1) {
+                geometries.push(nxGeometry.clone().applyMatrix4(matrix));
+            }
+
+            if ((pz !== h && pz !== h + 1) || z === worldDepth - 1) {
+                geometries.push(pzGeometry.clone().applyMatrix4(matrix));
+            }
+
+            if ((nz !== h && nz !== h + 1) || z === 0) {
+                geometries.push(nzGeometry.clone().applyMatrix4(matrix));
+            }
+        }
+    }
+
+    const geometry = BufferGeometryUtils.mergeGeometries(geometries);
+    geometry.computeBoundingSphere();
+
+    const texture = new THREE.TextureLoader().load('textures/minecraft/atlas.png');
+    texture.colorSpace = THREE.SRGBColorSpace;
+    texture.magFilter = THREE.NearestFilter;
+
+    const mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ map: texture, side: THREE.DoubleSide }));
+    scene.add(mesh);
+
+    const ambientLight = new THREE.AmbientLight(0xeeeeee, 3);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 12);
+    directionalLight.position.set(1, 1, 0.5).normalize();
+    scene.add(directionalLight);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+
+    controls.movementSpeed = 1000;
+    controls.lookSpeed = 0.125;
+    controls.lookVertical = true;
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+function generateHeight(width, height) {
+    const data = [],
+        perlin = new ImprovedNoise(),
+        size = width * height,
+        z = Math.random() * 100;
+
+    let quality = 2;
+
+    for (let j = 0; j < 4; j++) {
+        if (j === 0) for (let i = 0; i < size; i++) data[i] = 0;
+
+        for (let i = 0; i < size; i++) {
+            const x = i % width,
+                y = (i / width) | 0;
+            data[i] += perlin.noise(x / quality, y / quality, z) * quality;
+        }
+
+        quality *= 4;
+    }
+
+    return data;
+}
+
+function getY(x, z) {
+    return (data[x + z * worldWidth] * 0.15) | 0;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    controls.update(clock.getDelta());
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_nurbs.ts b/examples-testing/examples/webgl_geometry_nurbs.ts
new file mode 100644
index 000000000..a603710bd
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_nurbs.ts
@@ -0,0 +1,298 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
+import { NURBSSurface } from 'three/addons/curves/NURBSSurface.js';
+import { NURBSVolume } from 'three/addons/curves/NURBSVolume.js';
+import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+let group;
+
+let targetRotation = 0;
+let targetRotationOnPointerDown = 0;
+
+let pointerX = 0;
+let pointerXOnPointerDown = 0;
+
+let windowHalfX = window.innerWidth / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(0, 150, 750);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    scene.add(new THREE.AmbientLight(0xffffff));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    group = new THREE.Group();
+    group.position.y = 50;
+    scene.add(group);
+
+    // NURBS curve
+
+    const nurbsControlPoints = [];
+    const nurbsKnots = [];
+    const nurbsDegree = 3;
+
+    for (let i = 0; i <= nurbsDegree; i++) {
+        nurbsKnots.push(0);
+    }
+
+    for (let i = 0, j = 20; i < j; i++) {
+        nurbsControlPoints.push(
+            new THREE.Vector4(
+                Math.random() * 400 - 200,
+                Math.random() * 400,
+                Math.random() * 400 - 200,
+                1, // weight of control point: higher means stronger attraction
+            ),
+        );
+
+        const knot = (i + 1) / (j - nurbsDegree);
+        nurbsKnots.push(THREE.MathUtils.clamp(knot, 0, 1));
+    }
+
+    const nurbsCurve = new NURBSCurve(nurbsDegree, nurbsKnots, nurbsControlPoints);
+
+    const nurbsGeometry = new THREE.BufferGeometry();
+    nurbsGeometry.setFromPoints(nurbsCurve.getPoints(200));
+
+    const nurbsMaterial = new THREE.LineBasicMaterial({ color: 0x333333 });
+
+    const nurbsLine = new THREE.Line(nurbsGeometry, nurbsMaterial);
+    nurbsLine.position.set(0, -100, 0);
+    group.add(nurbsLine);
+
+    const nurbsControlPointsGeometry = new THREE.BufferGeometry();
+    nurbsControlPointsGeometry.setFromPoints(nurbsCurve.controlPoints);
+
+    const nurbsControlPointsMaterial = new THREE.LineBasicMaterial({
+        color: 0x333333,
+        opacity: 0.25,
+        transparent: true,
+    });
+
+    const nurbsControlPointsLine = new THREE.Line(nurbsControlPointsGeometry, nurbsControlPointsMaterial);
+    nurbsControlPointsLine.position.copy(nurbsLine.position);
+    group.add(nurbsControlPointsLine);
+
+    // NURBS surface
+    {
+        const nsControlPoints = [
+            [
+                new THREE.Vector4(-200, -200, 100, 1),
+                new THREE.Vector4(-200, -100, -200, 1),
+                new THREE.Vector4(-200, 100, 250, 1),
+                new THREE.Vector4(-200, 200, -100, 1),
+            ],
+            [
+                new THREE.Vector4(0, -200, 0, 1),
+                new THREE.Vector4(0, -100, -100, 5),
+                new THREE.Vector4(0, 100, 150, 5),
+                new THREE.Vector4(0, 200, 0, 1),
+            ],
+            [
+                new THREE.Vector4(200, -200, -100, 1),
+                new THREE.Vector4(200, -100, 200, 1),
+                new THREE.Vector4(200, 100, -250, 1),
+                new THREE.Vector4(200, 200, 100, 1),
+            ],
+        ];
+        const degree1 = 2;
+        const degree2 = 3;
+        const knots1 = [0, 0, 0, 1, 1, 1];
+        const knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
+        const nurbsSurface = new NURBSSurface(degree1, degree2, knots1, knots2, nsControlPoints);
+
+        const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+        map.wrapS = map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 16;
+        map.colorSpace = THREE.SRGBColorSpace;
+
+        function getSurfacePoint(u, v, target) {
+            return nurbsSurface.getPoint(u, v, target);
+        }
+
+        const geometry = new ParametricGeometry(getSurfacePoint, 20, 20);
+        const material = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const object = new THREE.Mesh(geometry, material);
+        object.position.set(-400, 100, 0);
+        object.scale.multiplyScalar(1);
+        group.add(object);
+    }
+
+    // NURBS volume
+    {
+        const nsControlPoints = [
+            [
+                [new THREE.Vector4(-200, -200, -200, 1), new THREE.Vector4(-200, -200, 200, 1)],
+                [new THREE.Vector4(-200, -100, -200, 1), new THREE.Vector4(-200, -100, 200, 1)],
+                [new THREE.Vector4(-200, 100, -200, 1), new THREE.Vector4(-200, 100, 200, 1)],
+                [new THREE.Vector4(-200, 200, -200, 1), new THREE.Vector4(-200, 200, 200, 1)],
+            ],
+            [
+                [new THREE.Vector4(0, -200, -200, 1), new THREE.Vector4(0, -200, 200, 1)],
+                [new THREE.Vector4(0, -100, -200, 1), new THREE.Vector4(0, -100, 200, 1)],
+                [new THREE.Vector4(0, 100, -200, 1), new THREE.Vector4(0, 100, 200, 1)],
+                [new THREE.Vector4(0, 200, -200, 1), new THREE.Vector4(0, 200, 200, 1)],
+            ],
+            [
+                [new THREE.Vector4(200, -200, -200, 1), new THREE.Vector4(200, -200, 200, 1)],
+                [new THREE.Vector4(200, -100, 0, 1), new THREE.Vector4(200, -100, 100, 1)],
+                [new THREE.Vector4(200, 100, 0, 1), new THREE.Vector4(200, 100, 100, 1)],
+                [new THREE.Vector4(200, 200, 0, 1), new THREE.Vector4(200, 200, 100, 1)],
+            ],
+        ];
+        const degree1 = 2;
+        const degree2 = 3;
+        const degree3 = 1;
+        const knots1 = [0, 0, 0, 1, 1, 1];
+        const knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
+        const knots3 = [0, 0, 1, 1];
+        const nurbsVolume = new NURBSVolume(degree1, degree2, degree3, knots1, knots2, knots3, nsControlPoints);
+
+        const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+        map.wrapS = map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 16;
+        map.colorSpace = THREE.SRGBColorSpace;
+
+        // Since ParametricGeometry() only support bi-variate parametric geometries
+        // we create evaluation functions for different surfaces with one of the three
+        // parameter values (u, v, w) kept constant and create multiple THREE.Mesh
+        // objects one for each surface
+        function getSurfacePointFront(u, v, target) {
+            return nurbsVolume.getPoint(u, v, 0, target);
+        }
+
+        function getSurfacePointMiddle(u, v, target) {
+            return nurbsVolume.getPoint(u, v, 0.5, target);
+        }
+
+        function getSurfacePointBack(u, v, target) {
+            return nurbsVolume.getPoint(u, v, 1, target);
+        }
+
+        function getSurfacePointTop(u, w, target) {
+            return nurbsVolume.getPoint(u, 1, w, target);
+        }
+
+        function getSurfacePointSide(v, w, target) {
+            return nurbsVolume.getPoint(0, v, w, target);
+        }
+
+        const geometryFront = new ParametricGeometry(getSurfacePointFront, 20, 20);
+        const materialFront = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const objectFront = new THREE.Mesh(geometryFront, materialFront);
+        objectFront.position.set(400, 100, 0);
+        objectFront.scale.multiplyScalar(0.5);
+        group.add(objectFront);
+
+        const geometryMiddle = new ParametricGeometry(getSurfacePointMiddle, 20, 20);
+        const materialMiddle = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const objectMiddle = new THREE.Mesh(geometryMiddle, materialMiddle);
+        objectMiddle.position.set(400, 100, 0);
+        objectMiddle.scale.multiplyScalar(0.5);
+        group.add(objectMiddle);
+
+        const geometryBack = new ParametricGeometry(getSurfacePointBack, 20, 20);
+        const materialBack = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const objectBack = new THREE.Mesh(geometryBack, materialBack);
+        objectBack.position.set(400, 100, 0);
+        objectBack.scale.multiplyScalar(0.5);
+        group.add(objectBack);
+
+        const geometryTop = new ParametricGeometry(getSurfacePointTop, 20, 20);
+        const materialTop = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const objectTop = new THREE.Mesh(geometryTop, materialTop);
+        objectTop.position.set(400, 100, 0);
+        objectTop.scale.multiplyScalar(0.5);
+        group.add(objectTop);
+
+        const geometrySide = new ParametricGeometry(getSurfacePointSide, 20, 20);
+        const materialSide = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
+        const objectSide = new THREE.Mesh(geometrySide, materialSide);
+        objectSide.position.set(400, 100, 0);
+        objectSide.scale.multiplyScalar(0.5);
+        group.add(objectSide);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointerdown', onPointerDown);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onPointerDown(event) {
+    if (event.isPrimary === false) return;
+
+    pointerXOnPointerDown = event.clientX - windowHalfX;
+    targetRotationOnPointerDown = targetRotation;
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    pointerX = event.clientX - windowHalfX;
+
+    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
+}
+
+function onPointerUp() {
+    if (event.isPrimary === false) return;
+
+    document.removeEventListener('pointermove', onPointerMove);
+    document.removeEventListener('pointerup', onPointerUp);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_sdf.ts b/examples-testing/examples/webgl_geometry_sdf.ts
new file mode 100644
index 000000000..fa5dba102
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_sdf.ts
@@ -0,0 +1,164 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { SDFGeometryGenerator } from 'three/addons/geometries/SDFGeometryGenerator.js';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let renderer, stats, meshFromSDF, scene, camera, clock, controls;
+
+const settings = {
+    res: 4,
+    bounds: 1,
+    autoRotate: true,
+    wireframe: true,
+    material: 'depth',
+    vertexCount: '0',
+};
+
+// Example SDF from https://www.shadertoy.com/view/MdXSWn -->
+
+const shader = /* glsl */ `
+				float dist(vec3 p) {
+					p.xyz = p.xzy;
+					p *= 1.2;
+					vec3 z = p;
+					vec3 dz=vec3(0.0);
+					float power = 8.0;
+					float r, theta, phi;
+					float dr = 1.0;
+					
+					float t0 = 1.0;
+					for(int i = 0; i < 7; ++i) {
+						r = length(z);
+						if(r > 2.0) continue;
+						theta = atan(z.y / z.x);
+						#ifdef phase_shift_on
+						phi = asin(z.z / r) ;
+						#else
+						phi = asin(z.z / r);
+						#endif
+						
+						dr = pow(r, power - 1.0) * dr * power + 1.0;
+					
+						r = pow(r, power);
+						theta = theta * power;
+						phi = phi * power;
+						
+						z = r * vec3(cos(theta)*cos(phi), sin(theta)*cos(phi), sin(phi)) + p;
+						
+						t0 = min(t0, r);
+					}
+		
+					return 0.5 * log(r) * r / dr;
+				}
+			`;
+
+init();
+
+function init() {
+    const w = window.innerWidth;
+    const h = window.innerHeight;
+
+    camera = new THREE.OrthographicCamera(w / -2, w / 2, h / 2, h / -2, 0.01, 1600);
+    camera.position.z = 1100;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const panel = new GUI();
+
+    panel.add(settings, 'res', 1, 6, 1).name('Res').onFinishChange(compile);
+    panel.add(settings, 'bounds', 1, 10, 1).name('Bounds').onFinishChange(compile);
+    panel.add(settings, 'material', ['depth', 'normal']).name('Material').onChange(setMaterial);
+    panel.add(settings, 'wireframe').name('Wireframe').onChange(setMaterial);
+    panel.add(settings, 'autoRotate').name('Auto Rotate');
+    panel.add(settings, 'vertexCount').name('Vertex count').listen().disable();
+
+    //
+
+    compile();
+}
+
+function compile() {
+    const generator = new SDFGeometryGenerator(renderer);
+    const geometry = generator.generate(Math.pow(2, settings.res + 2), shader, settings.bounds);
+    geometry.computeVertexNormals();
+
+    if (meshFromSDF) {
+        // updates mesh
+
+        meshFromSDF.geometry.dispose();
+        meshFromSDF.geometry = geometry;
+    } else {
+        // inits meshFromSDF : THREE.Mesh
+
+        meshFromSDF = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial());
+        scene.add(meshFromSDF);
+
+        const scale = (Math.min(window.innerWidth, window.innerHeight) / 2) * 0.66;
+        meshFromSDF.scale.set(scale, scale, scale);
+
+        setMaterial();
+    }
+
+    settings.vertexCount = geometry.attributes.position.count;
+}
+
+function setMaterial() {
+    meshFromSDF.material.dispose();
+
+    if (settings.material == 'depth') {
+        meshFromSDF.material = new THREE.MeshDepthMaterial();
+    } else if (settings.material == 'normal') {
+        meshFromSDF.material = new THREE.MeshNormalMaterial();
+    }
+
+    meshFromSDF.material.wireframe = settings.wireframe;
+}
+
+function onWindowResize() {
+    const w = window.innerWidth;
+    const h = window.innerHeight;
+
+    renderer.setSize(w, h);
+
+    camera.left = w / -2;
+    camera.right = w / 2;
+    camera.top = h / 2;
+    camera.bottom = h / -2;
+
+    camera.updateProjectionMatrix();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+function animate() {
+    controls.update();
+
+    if (settings.autoRotate) {
+        meshFromSDF.rotation.y += Math.PI * 0.05 * clock.getDelta();
+    }
+
+    render();
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_geometry_shapes.ts b/examples-testing/examples/webgl_geometry_shapes.ts
new file mode 100644
index 000000000..f1d00f011
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_shapes.ts
@@ -0,0 +1,363 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let group;
+
+let targetRotation = 0;
+let targetRotationOnPointerDown = 0;
+
+let pointerX = 0;
+let pointerXOnPointerDown = 0;
+
+let windowHalfX = window.innerWidth / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 150, 500);
+    scene.add(camera);
+
+    const light = new THREE.PointLight(0xffffff, 2.5, 0, 0);
+    camera.add(light);
+
+    group = new THREE.Group();
+    group.position.y = 50;
+    scene.add(group);
+
+    const loader = new THREE.TextureLoader();
+    const texture = loader.load('textures/uv_grid_opengl.jpg');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    // it's necessary to apply these settings in order to correctly display the texture on a shape geometry
+
+    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+    texture.repeat.set(0.008, 0.008);
+
+    function addShape(shape, extrudeSettings, color, x, y, z, rx, ry, rz, s) {
+        // flat shape with texture
+        // note: default UVs generated by THREE.ShapeGeometry are simply the x- and y-coordinates of the vertices
+
+        let geometry = new THREE.ShapeGeometry(shape);
+
+        let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ side: THREE.DoubleSide, map: texture }));
+        mesh.position.set(x, y, z - 175);
+        mesh.rotation.set(rx, ry, rz);
+        mesh.scale.set(s, s, s);
+        group.add(mesh);
+
+        // flat shape
+
+        geometry = new THREE.ShapeGeometry(shape);
+
+        mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: color, side: THREE.DoubleSide }));
+        mesh.position.set(x, y, z - 125);
+        mesh.rotation.set(rx, ry, rz);
+        mesh.scale.set(s, s, s);
+        group.add(mesh);
+
+        // extruded shape
+
+        geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
+
+        mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: color }));
+        mesh.position.set(x, y, z - 75);
+        mesh.rotation.set(rx, ry, rz);
+        mesh.scale.set(s, s, s);
+        group.add(mesh);
+
+        addLineShape(shape, color, x, y, z, rx, ry, rz, s);
+    }
+
+    function addLineShape(shape, color, x, y, z, rx, ry, rz, s) {
+        // lines
+
+        shape.autoClose = true;
+
+        const points = shape.getPoints();
+        const spacedPoints = shape.getSpacedPoints(50);
+
+        const geometryPoints = new THREE.BufferGeometry().setFromPoints(points);
+        const geometrySpacedPoints = new THREE.BufferGeometry().setFromPoints(spacedPoints);
+
+        // solid line
+
+        let line = new THREE.Line(geometryPoints, new THREE.LineBasicMaterial({ color: color }));
+        line.position.set(x, y, z - 25);
+        line.rotation.set(rx, ry, rz);
+        line.scale.set(s, s, s);
+        group.add(line);
+
+        // line from equidistance sampled points
+
+        line = new THREE.Line(geometrySpacedPoints, new THREE.LineBasicMaterial({ color: color }));
+        line.position.set(x, y, z + 25);
+        line.rotation.set(rx, ry, rz);
+        line.scale.set(s, s, s);
+        group.add(line);
+
+        // vertices from real points
+
+        let particles = new THREE.Points(geometryPoints, new THREE.PointsMaterial({ color: color, size: 4 }));
+        particles.position.set(x, y, z + 75);
+        particles.rotation.set(rx, ry, rz);
+        particles.scale.set(s, s, s);
+        group.add(particles);
+
+        // equidistance sampled points
+
+        particles = new THREE.Points(geometrySpacedPoints, new THREE.PointsMaterial({ color: color, size: 4 }));
+        particles.position.set(x, y, z + 125);
+        particles.rotation.set(rx, ry, rz);
+        particles.scale.set(s, s, s);
+        group.add(particles);
+    }
+
+    // California
+
+    const californiaPts = [];
+
+    californiaPts.push(new THREE.Vector2(610, 320));
+    californiaPts.push(new THREE.Vector2(450, 300));
+    californiaPts.push(new THREE.Vector2(392, 392));
+    californiaPts.push(new THREE.Vector2(266, 438));
+    californiaPts.push(new THREE.Vector2(190, 570));
+    californiaPts.push(new THREE.Vector2(190, 600));
+    californiaPts.push(new THREE.Vector2(160, 620));
+    californiaPts.push(new THREE.Vector2(160, 650));
+    californiaPts.push(new THREE.Vector2(180, 640));
+    californiaPts.push(new THREE.Vector2(165, 680));
+    californiaPts.push(new THREE.Vector2(150, 670));
+    californiaPts.push(new THREE.Vector2(90, 737));
+    californiaPts.push(new THREE.Vector2(80, 795));
+    californiaPts.push(new THREE.Vector2(50, 835));
+    californiaPts.push(new THREE.Vector2(64, 870));
+    californiaPts.push(new THREE.Vector2(60, 945));
+    californiaPts.push(new THREE.Vector2(300, 945));
+    californiaPts.push(new THREE.Vector2(300, 743));
+    californiaPts.push(new THREE.Vector2(600, 473));
+    californiaPts.push(new THREE.Vector2(626, 425));
+    californiaPts.push(new THREE.Vector2(600, 370));
+    californiaPts.push(new THREE.Vector2(610, 320));
+
+    for (let i = 0; i < californiaPts.length; i++) californiaPts[i].multiplyScalar(0.25);
+
+    const californiaShape = new THREE.Shape(californiaPts);
+
+    // Triangle
+
+    const triangleShape = new THREE.Shape().moveTo(80, 20).lineTo(40, 80).lineTo(120, 80).lineTo(80, 20); // close path
+
+    // Heart
+
+    const x = 0,
+        y = 0;
+
+    const heartShape = new THREE.Shape()
+        .moveTo(x + 25, y + 25)
+        .bezierCurveTo(x + 25, y + 25, x + 20, y, x, y)
+        .bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35)
+        .bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95)
+        .bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35)
+        .bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y)
+        .bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);
+
+    // Square
+
+    const sqLength = 80;
+
+    const squareShape = new THREE.Shape()
+        .moveTo(0, 0)
+        .lineTo(0, sqLength)
+        .lineTo(sqLength, sqLength)
+        .lineTo(sqLength, 0)
+        .lineTo(0, 0);
+
+    // Rounded rectangle
+
+    const roundedRectShape = new THREE.Shape();
+
+    (function roundedRect(ctx, x, y, width, height, radius) {
+        ctx.moveTo(x, y + radius);
+        ctx.lineTo(x, y + height - radius);
+        ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+        ctx.lineTo(x + width - radius, y + height);
+        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+        ctx.lineTo(x + width, y + radius);
+        ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+        ctx.lineTo(x + radius, y);
+        ctx.quadraticCurveTo(x, y, x, y + radius);
+    })(roundedRectShape, 0, 0, 50, 50, 20);
+
+    // Track
+
+    const trackShape = new THREE.Shape()
+        .moveTo(40, 40)
+        .lineTo(40, 160)
+        .absarc(60, 160, 20, Math.PI, 0, true)
+        .lineTo(80, 40)
+        .absarc(60, 40, 20, 2 * Math.PI, Math.PI, true);
+
+    // Circle
+
+    const circleRadius = 40;
+    const circleShape = new THREE.Shape()
+        .moveTo(0, circleRadius)
+        .quadraticCurveTo(circleRadius, circleRadius, circleRadius, 0)
+        .quadraticCurveTo(circleRadius, -circleRadius, 0, -circleRadius)
+        .quadraticCurveTo(-circleRadius, -circleRadius, -circleRadius, 0)
+        .quadraticCurveTo(-circleRadius, circleRadius, 0, circleRadius);
+
+    // Fish
+
+    const fishShape = new THREE.Shape()
+        .moveTo(x, y)
+        .quadraticCurveTo(x + 50, y - 80, x + 90, y - 10)
+        .quadraticCurveTo(x + 100, y - 10, x + 115, y - 40)
+        .quadraticCurveTo(x + 115, y, x + 115, y + 40)
+        .quadraticCurveTo(x + 100, y + 10, x + 90, y + 10)
+        .quadraticCurveTo(x + 50, y + 80, x, y);
+
+    // Arc circle
+
+    const arcShape = new THREE.Shape().moveTo(50, 10).absarc(10, 10, 40, 0, Math.PI * 2, false);
+
+    const holePath = new THREE.Path().moveTo(20, 10).absarc(10, 10, 10, 0, Math.PI * 2, true);
+
+    arcShape.holes.push(holePath);
+
+    // Smiley
+
+    const smileyShape = new THREE.Shape().moveTo(80, 40).absarc(40, 40, 40, 0, Math.PI * 2, false);
+
+    const smileyEye1Path = new THREE.Path().moveTo(35, 20).absellipse(25, 20, 10, 10, 0, Math.PI * 2, true);
+
+    const smileyEye2Path = new THREE.Path().moveTo(65, 20).absarc(55, 20, 10, 0, Math.PI * 2, true);
+
+    const smileyMouthPath = new THREE.Path()
+        .moveTo(20, 40)
+        .quadraticCurveTo(40, 60, 60, 40)
+        .bezierCurveTo(70, 45, 70, 50, 60, 60)
+        .quadraticCurveTo(40, 80, 20, 60)
+        .quadraticCurveTo(5, 50, 20, 40);
+
+    smileyShape.holes.push(smileyEye1Path);
+    smileyShape.holes.push(smileyEye2Path);
+    smileyShape.holes.push(smileyMouthPath);
+
+    // Spline shape
+
+    const splinepts = [];
+    splinepts.push(new THREE.Vector2(70, 20));
+    splinepts.push(new THREE.Vector2(80, 90));
+    splinepts.push(new THREE.Vector2(-30, 70));
+    splinepts.push(new THREE.Vector2(0, 0));
+
+    const splineShape = new THREE.Shape().moveTo(0, 0).splineThru(splinepts);
+
+    const extrudeSettings = {
+        depth: 8,
+        bevelEnabled: true,
+        bevelSegments: 2,
+        steps: 2,
+        bevelSize: 1,
+        bevelThickness: 1,
+    };
+
+    // addShape( shape, color, x, y, z, rx, ry,rz, s );
+
+    addShape(californiaShape, extrudeSettings, 0xf08000, -300, -100, 0, 0, 0, 0, 1);
+    addShape(triangleShape, extrudeSettings, 0x8080f0, -180, 0, 0, 0, 0, 0, 1);
+    addShape(roundedRectShape, extrudeSettings, 0x008000, -150, 150, 0, 0, 0, 0, 1);
+    addShape(trackShape, extrudeSettings, 0x008080, 200, -100, 0, 0, 0, 0, 1);
+    addShape(squareShape, extrudeSettings, 0x0040f0, 150, 100, 0, 0, 0, 0, 1);
+    addShape(heartShape, extrudeSettings, 0xf00000, 60, 100, 0, 0, 0, Math.PI, 1);
+    addShape(circleShape, extrudeSettings, 0x00f000, 120, 250, 0, 0, 0, 0, 1);
+    addShape(fishShape, extrudeSettings, 0x404040, -60, 200, 0, 0, 0, 0, 1);
+    addShape(smileyShape, extrudeSettings, 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
+    addShape(arcShape, extrudeSettings, 0x804000, 150, 0, 0, 0, 0, 0, 1);
+    addShape(splineShape, extrudeSettings, 0x808080, -50, -100, 0, 0, 0, 0, 1);
+
+    addLineShape(arcShape.holes[0], 0x804000, 150, 0, 0, 0, 0, 0, 1);
+
+    for (let i = 0; i < smileyShape.holes.length; i += 1) {
+        addLineShape(smileyShape.holes[i], 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointerdown', onPointerDown);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onPointerDown(event) {
+    if (event.isPrimary === false) return;
+
+    pointerXOnPointerDown = event.clientX - windowHalfX;
+    targetRotationOnPointerDown = targetRotation;
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    pointerX = event.clientX - windowHalfX;
+
+    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
+}
+
+function onPointerUp() {
+    if (event.isPrimary === false) return;
+
+    document.removeEventListener('pointermove', onPointerMove);
+    document.removeEventListener('pointerup', onPointerUp);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_teapot.ts b/examples-testing/examples/webgl_geometry_teapot.ts
new file mode 100644
index 000000000..4c884a559
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_teapot.ts
@@ -0,0 +1,180 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
+
+let camera, scene, renderer;
+let cameraControls;
+let effectController;
+const teapotSize = 300;
+let ambientLight, light;
+
+let tess = -1; // force initialization
+let bBottom;
+let bLid;
+let bBody;
+let bFitLid;
+let bNonBlinn;
+let shading;
+
+let teapot, textureCube;
+const materials = {};
+
+init();
+render();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    const canvasWidth = window.innerWidth;
+    const canvasHeight = window.innerHeight;
+
+    // CAMERA
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 80000);
+    camera.position.set(-600, 550, 1300);
+
+    // LIGHTS
+    ambientLight = new THREE.AmbientLight(0x7c7c7c, 3.0);
+
+    light = new THREE.DirectionalLight(0xffffff, 3.0);
+    light.position.set(0.32, 0.39, 0.7);
+
+    // RENDERER
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(canvasWidth, canvasHeight);
+    container.appendChild(renderer.domElement);
+
+    // EVENTS
+    window.addEventListener('resize', onWindowResize);
+
+    // CONTROLS
+    cameraControls = new OrbitControls(camera, renderer.domElement);
+    cameraControls.addEventListener('change', render);
+
+    // TEXTURE MAP
+    const textureMap = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    textureMap.wrapS = textureMap.wrapT = THREE.RepeatWrapping;
+    textureMap.anisotropy = 16;
+    textureMap.colorSpace = THREE.SRGBColorSpace;
+
+    // REFLECTION MAP
+    const path = 'textures/cube/pisa/';
+    const urls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
+
+    textureCube = new THREE.CubeTextureLoader().setPath(path).load(urls);
+
+    materials['wireframe'] = new THREE.MeshBasicMaterial({ wireframe: true });
+    materials['flat'] = new THREE.MeshPhongMaterial({ specular: 0x000000, flatShading: true, side: THREE.DoubleSide });
+    materials['smooth'] = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide });
+    materials['glossy'] = new THREE.MeshPhongMaterial({ side: THREE.DoubleSide });
+    materials['textured'] = new THREE.MeshPhongMaterial({ map: textureMap, side: THREE.DoubleSide });
+    materials['reflective'] = new THREE.MeshPhongMaterial({ envMap: textureCube, side: THREE.DoubleSide });
+
+    // scene itself
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xaaaaaa);
+
+    scene.add(ambientLight);
+    scene.add(light);
+
+    // GUI
+    setupGui();
+}
+
+// EVENT HANDLERS
+
+function onWindowResize() {
+    const canvasWidth = window.innerWidth;
+    const canvasHeight = window.innerHeight;
+
+    renderer.setSize(canvasWidth, canvasHeight);
+
+    camera.aspect = canvasWidth / canvasHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function setupGui() {
+    effectController = {
+        newTess: 15,
+        bottom: true,
+        lid: true,
+        body: true,
+        fitLid: false,
+        nonblinn: false,
+        newShading: 'glossy',
+    };
+
+    const gui = new GUI();
+    gui.add(effectController, 'newTess', [2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50])
+        .name('Tessellation Level')
+        .onChange(render);
+    gui.add(effectController, 'lid').name('display lid').onChange(render);
+    gui.add(effectController, 'body').name('display body').onChange(render);
+    gui.add(effectController, 'bottom').name('display bottom').onChange(render);
+    gui.add(effectController, 'fitLid').name('snug lid').onChange(render);
+    gui.add(effectController, 'nonblinn').name('original scale').onChange(render);
+    gui.add(effectController, 'newShading', ['wireframe', 'flat', 'smooth', 'glossy', 'textured', 'reflective'])
+        .name('Shading')
+        .onChange(render);
+}
+
+//
+
+function render() {
+    if (
+        effectController.newTess !== tess ||
+        effectController.bottom !== bBottom ||
+        effectController.lid !== bLid ||
+        effectController.body !== bBody ||
+        effectController.fitLid !== bFitLid ||
+        effectController.nonblinn !== bNonBlinn ||
+        effectController.newShading !== shading
+    ) {
+        tess = effectController.newTess;
+        bBottom = effectController.bottom;
+        bLid = effectController.lid;
+        bBody = effectController.body;
+        bFitLid = effectController.fitLid;
+        bNonBlinn = effectController.nonblinn;
+        shading = effectController.newShading;
+
+        createNewTeapot();
+    }
+
+    // skybox is rendered separately, so that it is always behind the teapot.
+    if (shading === 'reflective') {
+        scene.background = textureCube;
+    } else {
+        scene.background = null;
+    }
+
+    renderer.render(scene, camera);
+}
+
+// Whenever the teapot changes, the scene is rebuilt from scratch (not much to it).
+function createNewTeapot() {
+    if (teapot !== undefined) {
+        teapot.geometry.dispose();
+        scene.remove(teapot);
+    }
+
+    const geometry = new TeapotGeometry(
+        teapotSize,
+        tess,
+        effectController.bottom,
+        effectController.lid,
+        effectController.body,
+        effectController.fitLid,
+        !effectController.nonblinn,
+    );
+
+    teapot = new THREE.Mesh(geometry, materials[shading]);
+
+    scene.add(teapot);
+}
diff --git a/examples-testing/examples/webgl_geometry_terrain.ts b/examples-testing/examples/webgl_geometry_terrain.ts
new file mode 100644
index 000000000..8b6ed84ea
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_terrain.ts
@@ -0,0 +1,173 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+let container, stats;
+let camera, controls, scene, renderer;
+let mesh, texture;
+
+const worldWidth = 256,
+    worldDepth = 256;
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xefd1b5);
+    scene.fog = new THREE.FogExp2(0xefd1b5, 0.0025);
+
+    const data = generateHeight(worldWidth, worldDepth);
+
+    camera.position.set(100, 800, -800);
+    camera.lookAt(-100, 810, -800);
+
+    const geometry = new THREE.PlaneGeometry(7500, 7500, worldWidth - 1, worldDepth - 1);
+    geometry.rotateX(-Math.PI / 2);
+
+    const vertices = geometry.attributes.position.array;
+
+    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
+        vertices[j + 1] = data[i] * 10;
+    }
+
+    texture = new THREE.CanvasTexture(generateTexture(data, worldWidth, worldDepth));
+    texture.wrapS = THREE.ClampToEdgeWrapping;
+    texture.wrapT = THREE.ClampToEdgeWrapping;
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map: texture }));
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+    controls.movementSpeed = 150;
+    controls.lookSpeed = 0.1;
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+function generateHeight(width, height) {
+    let seed = Math.PI / 4;
+    window.Math.random = function () {
+        const x = Math.sin(seed++) * 10000;
+        return x - Math.floor(x);
+    };
+
+    const size = width * height,
+        data = new Uint8Array(size);
+    const perlin = new ImprovedNoise(),
+        z = Math.random() * 100;
+
+    let quality = 1;
+
+    for (let j = 0; j < 4; j++) {
+        for (let i = 0; i < size; i++) {
+            const x = i % width,
+                y = ~~(i / width);
+            data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality * 1.75);
+        }
+
+        quality *= 5;
+    }
+
+    return data;
+}
+
+function generateTexture(data, width, height) {
+    let context, image, imageData, shade;
+
+    const vector3 = new THREE.Vector3(0, 0, 0);
+
+    const sun = new THREE.Vector3(1, 1, 1);
+    sun.normalize();
+
+    const canvas = document.createElement('canvas');
+    canvas.width = width;
+    canvas.height = height;
+
+    context = canvas.getContext('2d');
+    context.fillStyle = '#000';
+    context.fillRect(0, 0, width, height);
+
+    image = context.getImageData(0, 0, canvas.width, canvas.height);
+    imageData = image.data;
+
+    for (let i = 0, j = 0, l = imageData.length; i < l; i += 4, j++) {
+        vector3.x = data[j - 2] - data[j + 2];
+        vector3.y = 2;
+        vector3.z = data[j - width * 2] - data[j + width * 2];
+        vector3.normalize();
+
+        shade = vector3.dot(sun);
+
+        imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
+        imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
+        imageData[i + 2] = shade * 96 * (0.5 + data[j] * 0.007);
+    }
+
+    context.putImageData(image, 0, 0);
+
+    // Scaled 4x
+
+    const canvasScaled = document.createElement('canvas');
+    canvasScaled.width = width * 4;
+    canvasScaled.height = height * 4;
+
+    context = canvasScaled.getContext('2d');
+    context.scale(4, 4);
+    context.drawImage(canvas, 0, 0);
+
+    image = context.getImageData(0, 0, canvasScaled.width, canvasScaled.height);
+    imageData = image.data;
+
+    for (let i = 0, l = imageData.length; i < l; i += 4) {
+        const v = ~~(Math.random() * 5);
+
+        imageData[i] += v;
+        imageData[i + 1] += v;
+        imageData[i + 2] += v;
+    }
+
+    context.putImageData(image, 0, 0);
+
+    return canvasScaled;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    controls.update(clock.getDelta());
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_terrain_raycast.ts b/examples-testing/examples/webgl_geometry_terrain_raycast.ts
new file mode 100644
index 000000000..f1383c138
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_terrain_raycast.ts
@@ -0,0 +1,206 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+let container, stats;
+
+let camera, controls, scene, renderer;
+
+let mesh, texture;
+
+const worldWidth = 256,
+    worldDepth = 256,
+    worldHalfWidth = worldWidth / 2,
+    worldHalfDepth = worldDepth / 2;
+
+let helper;
+
+const raycaster = new THREE.Raycaster();
+const pointer = new THREE.Vector2();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+    container.innerHTML = '';
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xbfd1e5);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 10, 20000);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1000;
+    controls.maxDistance = 10000;
+    controls.maxPolarAngle = Math.PI / 2;
+
+    //
+
+    const data = generateHeight(worldWidth, worldDepth);
+
+    controls.target.y = data[worldHalfWidth + worldHalfDepth * worldWidth] + 500;
+    camera.position.y = controls.target.y + 2000;
+    camera.position.x = 2000;
+    controls.update();
+
+    const geometry = new THREE.PlaneGeometry(7500, 7500, worldWidth - 1, worldDepth - 1);
+    geometry.rotateX(-Math.PI / 2);
+
+    const vertices = geometry.attributes.position.array;
+
+    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
+        vertices[j + 1] = data[i] * 10;
+    }
+
+    //
+
+    texture = new THREE.CanvasTexture(generateTexture(data, worldWidth, worldDepth));
+    texture.wrapS = THREE.ClampToEdgeWrapping;
+    texture.wrapT = THREE.ClampToEdgeWrapping;
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map: texture }));
+    scene.add(mesh);
+
+    const geometryHelper = new THREE.ConeGeometry(20, 100, 3);
+    geometryHelper.translate(0, 50, 0);
+    geometryHelper.rotateX(Math.PI / 2);
+    helper = new THREE.Mesh(geometryHelper, new THREE.MeshNormalMaterial());
+    scene.add(helper);
+
+    container.addEventListener('pointermove', onPointerMove);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function generateHeight(width, height) {
+    const size = width * height,
+        data = new Uint8Array(size),
+        perlin = new ImprovedNoise(),
+        z = Math.random() * 100;
+
+    let quality = 1;
+
+    for (let j = 0; j < 4; j++) {
+        for (let i = 0; i < size; i++) {
+            const x = i % width,
+                y = ~~(i / width);
+            data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality * 1.75);
+        }
+
+        quality *= 5;
+    }
+
+    return data;
+}
+
+function generateTexture(data, width, height) {
+    // bake lighting into texture
+
+    let context, image, imageData, shade;
+
+    const vector3 = new THREE.Vector3(0, 0, 0);
+
+    const sun = new THREE.Vector3(1, 1, 1);
+    sun.normalize();
+
+    const canvas = document.createElement('canvas');
+    canvas.width = width;
+    canvas.height = height;
+
+    context = canvas.getContext('2d');
+    context.fillStyle = '#000';
+    context.fillRect(0, 0, width, height);
+
+    image = context.getImageData(0, 0, canvas.width, canvas.height);
+    imageData = image.data;
+
+    for (let i = 0, j = 0, l = imageData.length; i < l; i += 4, j++) {
+        vector3.x = data[j - 2] - data[j + 2];
+        vector3.y = 2;
+        vector3.z = data[j - width * 2] - data[j + width * 2];
+        vector3.normalize();
+
+        shade = vector3.dot(sun);
+
+        imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
+        imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
+        imageData[i + 2] = shade * 96 * (0.5 + data[j] * 0.007);
+    }
+
+    context.putImageData(image, 0, 0);
+
+    // Scaled 4x
+
+    const canvasScaled = document.createElement('canvas');
+    canvasScaled.width = width * 4;
+    canvasScaled.height = height * 4;
+
+    context = canvasScaled.getContext('2d');
+    context.scale(4, 4);
+    context.drawImage(canvas, 0, 0);
+
+    image = context.getImageData(0, 0, canvasScaled.width, canvasScaled.height);
+    imageData = image.data;
+
+    for (let i = 0, l = imageData.length; i < l; i += 4) {
+        const v = ~~(Math.random() * 5);
+
+        imageData[i] += v;
+        imageData[i + 1] += v;
+        imageData[i + 2] += v;
+    }
+
+    context.putImageData(image, 0, 0);
+
+    return canvasScaled;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
+    pointer.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1;
+    raycaster.setFromCamera(pointer, camera);
+
+    // See if the ray from the camera into the world hits one of our meshes
+    const intersects = raycaster.intersectObject(mesh);
+
+    // Toggle rotation bool for meshes that we clicked
+    if (intersects.length > 0) {
+        helper.position.set(0, 0, 0);
+        helper.lookAt(intersects[0].face.normal);
+
+        helper.position.copy(intersects[0].point);
+    }
+}
diff --git a/examples-testing/examples/webgl_geometry_text.ts b/examples-testing/examples/webgl_geometry_text.ts
new file mode 100644
index 000000000..831ebcd6b
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_text.ts
@@ -0,0 +1,312 @@
+import * as THREE from 'three';
+
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+THREE.Cache.enabled = true;
+
+let container;
+
+let camera, cameraTarget, scene, renderer;
+
+let group, textMesh1, textMesh2, textGeo, materials;
+
+let firstLetter = true;
+
+let text = 'three.js',
+    bevelEnabled = true,
+    font = undefined,
+    fontName = 'optimer', // helvetiker, optimer, gentilis, droid sans, droid serif
+    fontWeight = 'bold'; // normal bold
+
+const depth = 20,
+    size = 70,
+    hover = 30,
+    curveSegments = 4,
+    bevelThickness = 2,
+    bevelSize = 1.5;
+
+const mirror = true;
+
+const fontMap = {
+    helvetiker: 0,
+    optimer: 1,
+    gentilis: 2,
+    'droid/droid_sans': 3,
+    'droid/droid_serif': 4,
+};
+
+const weightMap = {
+    regular: 0,
+    bold: 1,
+};
+
+const reverseFontMap = [];
+const reverseWeightMap = [];
+
+for (const i in fontMap) reverseFontMap[fontMap[i]] = i;
+for (const i in weightMap) reverseWeightMap[weightMap[i]] = i;
+
+let targetRotation = 0;
+let targetRotationOnPointerDown = 0;
+
+let pointerX = 0;
+let pointerXOnPointerDown = 0;
+
+let windowHalfX = window.innerWidth / 2;
+
+let fontIndex = 1;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
+    camera.position.set(0, 400, 700);
+
+    cameraTarget = new THREE.Vector3(0, 150, 0);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x000000);
+    scene.fog = new THREE.Fog(0x000000, 250, 1400);
+
+    // LIGHTS
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 0.4);
+    dirLight.position.set(0, 0, 1).normalize();
+    scene.add(dirLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 4.5, 0, 0);
+    pointLight.color.setHSL(Math.random(), 1, 0.5);
+    pointLight.position.set(0, 100, 90);
+    scene.add(pointLight);
+
+    materials = [
+        new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true }), // front
+        new THREE.MeshPhongMaterial({ color: 0xffffff }), // side
+    ];
+
+    group = new THREE.Group();
+    group.position.y = 100;
+
+    scene.add(group);
+
+    loadFont();
+
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(10000, 10000),
+        new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }),
+    );
+    plane.position.y = 100;
+    plane.rotation.x = -Math.PI / 2;
+    scene.add(plane);
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // EVENTS
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointerdown', onPointerDown);
+
+    document.addEventListener('keypress', onDocumentKeyPress);
+    document.addEventListener('keydown', onDocumentKeyDown);
+
+    //
+
+    const params = {
+        changeColor: function () {
+            pointLight.color.setHSL(Math.random(), 1, 0.5);
+        },
+        changeFont: function () {
+            fontIndex++;
+
+            fontName = reverseFontMap[fontIndex % reverseFontMap.length];
+
+            loadFont();
+        },
+        changeWeight: function () {
+            if (fontWeight === 'bold') {
+                fontWeight = 'regular';
+            } else {
+                fontWeight = 'bold';
+            }
+
+            loadFont();
+        },
+        changeBevel: function () {
+            bevelEnabled = !bevelEnabled;
+
+            refreshText();
+        },
+    };
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'changeColor').name('change color');
+    gui.add(params, 'changeFont').name('change font');
+    gui.add(params, 'changeWeight').name('change weight');
+    gui.add(params, 'changeBevel').name('change bevel');
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onDocumentKeyDown(event) {
+    if (firstLetter) {
+        firstLetter = false;
+        text = '';
+    }
+
+    const keyCode = event.keyCode;
+
+    // backspace
+
+    if (keyCode == 8) {
+        event.preventDefault();
+
+        text = text.substring(0, text.length - 1);
+        refreshText();
+
+        return false;
+    }
+}
+
+function onDocumentKeyPress(event) {
+    const keyCode = event.which;
+
+    // backspace
+
+    if (keyCode == 8) {
+        event.preventDefault();
+    } else {
+        const ch = String.fromCharCode(keyCode);
+        text += ch;
+
+        refreshText();
+    }
+}
+
+function loadFont() {
+    const loader = new FontLoader();
+    loader.load('fonts/' + fontName + '_' + fontWeight + '.typeface.json', function (response) {
+        font = response;
+
+        refreshText();
+    });
+}
+
+function createText() {
+    textGeo = new TextGeometry(text, {
+        font: font,
+
+        size: size,
+        depth: depth,
+        curveSegments: curveSegments,
+
+        bevelThickness: bevelThickness,
+        bevelSize: bevelSize,
+        bevelEnabled: bevelEnabled,
+    });
+
+    textGeo.computeBoundingBox();
+
+    const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
+
+    textMesh1 = new THREE.Mesh(textGeo, materials);
+
+    textMesh1.position.x = centerOffset;
+    textMesh1.position.y = hover;
+    textMesh1.position.z = 0;
+
+    textMesh1.rotation.x = 0;
+    textMesh1.rotation.y = Math.PI * 2;
+
+    group.add(textMesh1);
+
+    if (mirror) {
+        textMesh2 = new THREE.Mesh(textGeo, materials);
+
+        textMesh2.position.x = centerOffset;
+        textMesh2.position.y = -hover;
+        textMesh2.position.z = depth;
+
+        textMesh2.rotation.x = Math.PI;
+        textMesh2.rotation.y = Math.PI * 2;
+
+        group.add(textMesh2);
+    }
+}
+
+function refreshText() {
+    group.remove(textMesh1);
+    if (mirror) group.remove(textMesh2);
+
+    if (!text) return;
+
+    createText();
+}
+
+function onPointerDown(event) {
+    if (event.isPrimary === false) return;
+
+    pointerXOnPointerDown = event.clientX - windowHalfX;
+    targetRotationOnPointerDown = targetRotation;
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    pointerX = event.clientX - windowHalfX;
+
+    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
+}
+
+function onPointerUp() {
+    if (event.isPrimary === false) return;
+
+    document.removeEventListener('pointermove', onPointerMove);
+    document.removeEventListener('pointerup', onPointerUp);
+}
+
+//
+
+function animate() {
+    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
+
+    camera.lookAt(cameraTarget);
+
+    renderer.clear();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_text_shapes.ts b/examples-testing/examples/webgl_geometry_text_shapes.ts
new file mode 100644
index 000000000..adfb6008d
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_text_shapes.ts
@@ -0,0 +1,112 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(0, -400, 600);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const loader = new FontLoader();
+    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
+        const color = 0x006699;
+
+        const matDark = new THREE.LineBasicMaterial({
+            color: color,
+            side: THREE.DoubleSide,
+        });
+
+        const matLite = new THREE.MeshBasicMaterial({
+            color: color,
+            transparent: true,
+            opacity: 0.4,
+            side: THREE.DoubleSide,
+        });
+
+        const message = '   Three.js\nSimple text.';
+
+        const shapes = font.generateShapes(message, 100);
+
+        const geometry = new THREE.ShapeGeometry(shapes);
+
+        geometry.computeBoundingBox();
+
+        const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
+
+        geometry.translate(xMid, 0, 0);
+
+        // make shape ( N.B. edge view not visible )
+
+        const text = new THREE.Mesh(geometry, matLite);
+        text.position.z = -150;
+        scene.add(text);
+
+        // make line shape ( N.B. edge view remains visible )
+
+        const holeShapes = [];
+
+        for (let i = 0; i < shapes.length; i++) {
+            const shape = shapes[i];
+
+            if (shape.holes && shape.holes.length > 0) {
+                for (let j = 0; j < shape.holes.length; j++) {
+                    const hole = shape.holes[j];
+                    holeShapes.push(hole);
+                }
+            }
+        }
+
+        shapes.push.apply(shapes, holeShapes);
+
+        const lineText = new THREE.Object3D();
+
+        for (let i = 0; i < shapes.length; i++) {
+            const shape = shapes[i];
+
+            const points = shape.getPoints();
+            const geometry = new THREE.BufferGeometry().setFromPoints(points);
+
+            geometry.translate(xMid, 0, 0);
+
+            const lineMesh = new THREE.Line(geometry, matDark);
+            lineText.add(lineMesh);
+        }
+
+        scene.add(lineText);
+
+        render();
+    }); //end load function
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0, 0);
+    controls.update();
+
+    controls.addEventListener('change', render);
+
+    window.addEventListener('resize', onWindowResize);
+} // end init
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_geometry_text_stroke.ts b/examples-testing/examples/webgl_geometry_text_stroke.ts
new file mode 100644
index 000000000..9a1983253
--- /dev/null
+++ b/examples-testing/examples/webgl_geometry_text_stroke.ts
@@ -0,0 +1,116 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(0, -400, 600);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const loader = new FontLoader();
+    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
+        const color = new THREE.Color(0x006699);
+
+        const matDark = new THREE.MeshBasicMaterial({
+            color: color,
+            side: THREE.DoubleSide,
+        });
+
+        const matLite = new THREE.MeshBasicMaterial({
+            color: color,
+            transparent: true,
+            opacity: 0.4,
+            side: THREE.DoubleSide,
+        });
+
+        const message = '   Three.js\nStroke text.';
+
+        const shapes = font.generateShapes(message, 100);
+
+        const geometry = new THREE.ShapeGeometry(shapes);
+
+        geometry.computeBoundingBox();
+
+        const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
+
+        geometry.translate(xMid, 0, 0);
+
+        // make shape ( N.B. edge view not visible )
+
+        const text = new THREE.Mesh(geometry, matLite);
+        text.position.z = -150;
+        scene.add(text);
+
+        // make line shape ( N.B. edge view remains visible )
+
+        const holeShapes = [];
+
+        for (let i = 0; i < shapes.length; i++) {
+            const shape = shapes[i];
+
+            if (shape.holes && shape.holes.length > 0) {
+                for (let j = 0; j < shape.holes.length; j++) {
+                    const hole = shape.holes[j];
+                    holeShapes.push(hole);
+                }
+            }
+        }
+
+        shapes.push.apply(shapes, holeShapes);
+
+        const style = SVGLoader.getStrokeStyle(5, color.getStyle());
+
+        const strokeText = new THREE.Group();
+
+        for (let i = 0; i < shapes.length; i++) {
+            const shape = shapes[i];
+
+            const points = shape.getPoints();
+
+            const geometry = SVGLoader.pointsToStroke(points, style);
+
+            geometry.translate(xMid, 0, 0);
+
+            const strokeMesh = new THREE.Mesh(geometry, matDark);
+            strokeText.add(strokeMesh);
+        }
+
+        scene.add(strokeText);
+
+        render();
+    }); //end load function
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0, 0);
+    controls.update();
+
+    controls.addEventListener('change', render);
+
+    window.addEventListener('resize', onWindowResize);
+} // end init
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_gpgpu_birds.ts b/examples-testing/examples/webgl_gpgpu_birds.ts
new file mode 100644
index 000000000..20a5e0d97
--- /dev/null
+++ b/examples-testing/examples/webgl_gpgpu_birds.ts
@@ -0,0 +1,313 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
+
+/* TEXTURE WIDTH FOR SIMULATION */
+const WIDTH = 32;
+
+const BIRDS = WIDTH * WIDTH;
+
+// Custom Geometry - using 3 triangles each. No UVs, no normals currently.
+class BirdGeometry extends THREE.BufferGeometry {
+    constructor() {
+        super();
+
+        const trianglesPerBird = 3;
+        const triangles = BIRDS * trianglesPerBird;
+        const points = triangles * 3;
+
+        const vertices = new THREE.BufferAttribute(new Float32Array(points * 3), 3);
+        const birdColors = new THREE.BufferAttribute(new Float32Array(points * 3), 3);
+        const references = new THREE.BufferAttribute(new Float32Array(points * 2), 2);
+        const birdVertex = new THREE.BufferAttribute(new Float32Array(points), 1);
+
+        this.setAttribute('position', vertices);
+        this.setAttribute('birdColor', birdColors);
+        this.setAttribute('reference', references);
+        this.setAttribute('birdVertex', birdVertex);
+
+        // this.setAttribute( 'normal', new Float32Array( points * 3 ), 3 );
+
+        let v = 0;
+
+        function verts_push() {
+            for (let i = 0; i < arguments.length; i++) {
+                vertices.array[v++] = arguments[i];
+            }
+        }
+
+        const wingsSpan = 20;
+
+        for (let f = 0; f < BIRDS; f++) {
+            // Body
+
+            verts_push(0, -0, -20, 0, 4, -20, 0, 0, 30);
+
+            // Wings
+
+            verts_push(0, 0, -15, -wingsSpan, 0, 0, 0, 0, 15);
+
+            verts_push(0, 0, 15, wingsSpan, 0, 0, 0, 0, -15);
+        }
+
+        for (let v = 0; v < triangles * 3; v++) {
+            const triangleIndex = ~~(v / 3);
+            const birdIndex = ~~(triangleIndex / trianglesPerBird);
+            const x = (birdIndex % WIDTH) / WIDTH;
+            const y = ~~(birdIndex / WIDTH) / WIDTH;
+
+            const c = new THREE.Color(0x666666 + (~~(v / 9) / BIRDS) * 0x666666);
+
+            birdColors.array[v * 3 + 0] = c.r;
+            birdColors.array[v * 3 + 1] = c.g;
+            birdColors.array[v * 3 + 2] = c.b;
+
+            references.array[v * 2] = x;
+            references.array[v * 2 + 1] = y;
+
+            birdVertex.array[v] = v % 9;
+        }
+
+        this.scale(0.2, 0.2, 0.2);
+    }
+}
+
+//
+
+let container, stats;
+let camera, scene, renderer;
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+const BOUNDS = 800,
+    BOUNDS_HALF = BOUNDS / 2;
+
+let last = performance.now();
+
+let gpuCompute;
+let velocityVariable;
+let positionVariable;
+let positionUniforms;
+let velocityUniforms;
+let birdUniforms;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
+    camera.position.z = 350;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+    scene.fog = new THREE.Fog(0xffffff, 100, 1000);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    initComputeRenderer();
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    const effectController = {
+        separation: 20.0,
+        alignment: 20.0,
+        cohesion: 20.0,
+        freedom: 0.75,
+    };
+
+    const valuesChanger = function () {
+        velocityUniforms['separationDistance'].value = effectController.separation;
+        velocityUniforms['alignmentDistance'].value = effectController.alignment;
+        velocityUniforms['cohesionDistance'].value = effectController.cohesion;
+        velocityUniforms['freedomFactor'].value = effectController.freedom;
+    };
+
+    valuesChanger();
+
+    gui.add(effectController, 'separation', 0.0, 100.0, 1.0).onChange(valuesChanger);
+    gui.add(effectController, 'alignment', 0.0, 100, 0.001).onChange(valuesChanger);
+    gui.add(effectController, 'cohesion', 0.0, 100, 0.025).onChange(valuesChanger);
+    gui.close();
+
+    initBirds();
+}
+
+function initComputeRenderer() {
+    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
+
+    const dtPosition = gpuCompute.createTexture();
+    const dtVelocity = gpuCompute.createTexture();
+    fillPositionTexture(dtPosition);
+    fillVelocityTexture(dtVelocity);
+
+    velocityVariable = gpuCompute.addVariable(
+        'textureVelocity',
+        document.getElementById('fragmentShaderVelocity').textContent,
+        dtVelocity,
+    );
+    positionVariable = gpuCompute.addVariable(
+        'texturePosition',
+        document.getElementById('fragmentShaderPosition').textContent,
+        dtPosition,
+    );
+
+    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
+    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
+
+    positionUniforms = positionVariable.material.uniforms;
+    velocityUniforms = velocityVariable.material.uniforms;
+
+    positionUniforms['time'] = { value: 0.0 };
+    positionUniforms['delta'] = { value: 0.0 };
+    velocityUniforms['time'] = { value: 1.0 };
+    velocityUniforms['delta'] = { value: 0.0 };
+    velocityUniforms['testing'] = { value: 1.0 };
+    velocityUniforms['separationDistance'] = { value: 1.0 };
+    velocityUniforms['alignmentDistance'] = { value: 1.0 };
+    velocityUniforms['cohesionDistance'] = { value: 1.0 };
+    velocityUniforms['freedomFactor'] = { value: 1.0 };
+    velocityUniforms['predator'] = { value: new THREE.Vector3() };
+    velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed(2);
+
+    velocityVariable.wrapS = THREE.RepeatWrapping;
+    velocityVariable.wrapT = THREE.RepeatWrapping;
+    positionVariable.wrapS = THREE.RepeatWrapping;
+    positionVariable.wrapT = THREE.RepeatWrapping;
+
+    const error = gpuCompute.init();
+
+    if (error !== null) {
+        console.error(error);
+    }
+}
+
+function initBirds() {
+    const geometry = new BirdGeometry();
+
+    // For Vertex and Fragment
+    birdUniforms = {
+        color: { value: new THREE.Color(0xff2200) },
+        texturePosition: { value: null },
+        textureVelocity: { value: null },
+        time: { value: 1.0 },
+        delta: { value: 0.0 },
+    };
+
+    // THREE.ShaderMaterial
+    const material = new THREE.ShaderMaterial({
+        uniforms: birdUniforms,
+        vertexShader: document.getElementById('birdVS').textContent,
+        fragmentShader: document.getElementById('birdFS').textContent,
+        side: THREE.DoubleSide,
+    });
+
+    const birdMesh = new THREE.Mesh(geometry, material);
+    birdMesh.rotation.y = Math.PI / 2;
+    birdMesh.matrixAutoUpdate = false;
+    birdMesh.updateMatrix();
+
+    scene.add(birdMesh);
+}
+
+function fillPositionTexture(texture) {
+    const theArray = texture.image.data;
+
+    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
+        const x = Math.random() * BOUNDS - BOUNDS_HALF;
+        const y = Math.random() * BOUNDS - BOUNDS_HALF;
+        const z = Math.random() * BOUNDS - BOUNDS_HALF;
+
+        theArray[k + 0] = x;
+        theArray[k + 1] = y;
+        theArray[k + 2] = z;
+        theArray[k + 3] = 1;
+    }
+}
+
+function fillVelocityTexture(texture) {
+    const theArray = texture.image.data;
+
+    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
+        const x = Math.random() - 0.5;
+        const y = Math.random() - 0.5;
+        const z = Math.random() - 0.5;
+
+        theArray[k + 0] = x * 10;
+        theArray[k + 1] = y * 10;
+        theArray[k + 2] = z * 10;
+        theArray[k + 3] = 1;
+    }
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const now = performance.now();
+    let delta = (now - last) / 1000;
+
+    if (delta > 1) delta = 1; // safety cap on large deltas
+    last = now;
+
+    positionUniforms['time'].value = now;
+    positionUniforms['delta'].value = delta;
+    velocityUniforms['time'].value = now;
+    velocityUniforms['delta'].value = delta;
+    birdUniforms['time'].value = now;
+    birdUniforms['delta'].value = delta;
+
+    velocityUniforms['predator'].value.set((0.5 * mouseX) / windowHalfX, (-0.5 * mouseY) / windowHalfY, 0);
+
+    mouseX = 10000;
+    mouseY = 10000;
+
+    gpuCompute.compute();
+
+    birdUniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
+    birdUniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_gpgpu_birds_gltf.ts b/examples-testing/examples/webgl_gpgpu_birds_gltf.ts
new file mode 100644
index 000000000..3176b95a9
--- /dev/null
+++ b/examples-testing/examples/webgl_gpgpu_birds_gltf.ts
@@ -0,0 +1,415 @@
+import * as THREE from 'three';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
+
+/* TEXTURE WIDTH FOR SIMULATION */
+const WIDTH = 64;
+const BIRDS = WIDTH * WIDTH;
+
+/* BAKE ANIMATION INTO TEXTURE and CREATE GEOMETRY FROM BASE MODEL */
+const BirdGeometry = new THREE.BufferGeometry();
+let textureAnimation, durationAnimation, birdMesh, materialShader, indicesPerBird;
+
+function nextPowerOf2(n) {
+    return Math.pow(2, Math.ceil(Math.log(n) / Math.log(2)));
+}
+
+Math.lerp = function (value1, value2, amount) {
+    amount = Math.max(Math.min(amount, 1), 0);
+    return value1 + (value2 - value1) * amount;
+};
+
+const gltfs = ['models/gltf/Parrot.glb', 'models/gltf/Flamingo.glb'];
+const colors = [0xccffff, 0xffdeff];
+const sizes = [0.2, 0.1];
+const selectModel = Math.floor(Math.random() * gltfs.length);
+new GLTFLoader().load(gltfs[selectModel], function (gltf) {
+    const animations = gltf.animations;
+    durationAnimation = Math.round(animations[0].duration * 60);
+    const birdGeo = gltf.scene.children[0].geometry;
+    const morphAttributes = birdGeo.morphAttributes.position;
+    const tHeight = nextPowerOf2(durationAnimation);
+    const tWidth = nextPowerOf2(birdGeo.getAttribute('position').count);
+    indicesPerBird = birdGeo.index.count;
+    const tData = new Float32Array(4 * tWidth * tHeight);
+
+    for (let i = 0; i < tWidth; i++) {
+        for (let j = 0; j < tHeight; j++) {
+            const offset = j * tWidth * 4;
+
+            const curMorph = Math.floor((j / durationAnimation) * morphAttributes.length);
+            const nextMorph =
+                (Math.floor((j / durationAnimation) * morphAttributes.length) + 1) % morphAttributes.length;
+            const lerpAmount = ((j / durationAnimation) * morphAttributes.length) % 1;
+
+            if (j < durationAnimation) {
+                let d0, d1;
+
+                d0 = morphAttributes[curMorph].array[i * 3];
+                d1 = morphAttributes[nextMorph].array[i * 3];
+
+                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4] = Math.lerp(d0, d1, lerpAmount);
+
+                d0 = morphAttributes[curMorph].array[i * 3 + 1];
+                d1 = morphAttributes[nextMorph].array[i * 3 + 1];
+
+                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4 + 1] = Math.lerp(d0, d1, lerpAmount);
+
+                d0 = morphAttributes[curMorph].array[i * 3 + 2];
+                d1 = morphAttributes[nextMorph].array[i * 3 + 2];
+
+                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4 + 2] = Math.lerp(d0, d1, lerpAmount);
+
+                tData[offset + i * 4 + 3] = 1;
+            }
+        }
+    }
+
+    textureAnimation = new THREE.DataTexture(tData, tWidth, tHeight, THREE.RGBAFormat, THREE.FloatType);
+    textureAnimation.needsUpdate = true;
+
+    const vertices = [],
+        color = [],
+        reference = [],
+        seeds = [],
+        indices = [];
+    const totalVertices = birdGeo.getAttribute('position').count * 3 * BIRDS;
+    for (let i = 0; i < totalVertices; i++) {
+        const bIndex = i % (birdGeo.getAttribute('position').count * 3);
+        vertices.push(birdGeo.getAttribute('position').array[bIndex]);
+        color.push(birdGeo.getAttribute('color').array[bIndex]);
+    }
+
+    let r = Math.random();
+    for (let i = 0; i < birdGeo.getAttribute('position').count * BIRDS; i++) {
+        const bIndex = i % birdGeo.getAttribute('position').count;
+        const bird = Math.floor(i / birdGeo.getAttribute('position').count);
+        if (bIndex == 0) r = Math.random();
+        const j = ~~bird;
+        const x = (j % WIDTH) / WIDTH;
+        const y = ~~(j / WIDTH) / WIDTH;
+        reference.push(x, y, bIndex / tWidth, durationAnimation / tHeight);
+        seeds.push(bird, r, Math.random(), Math.random());
+    }
+
+    for (let i = 0; i < birdGeo.index.array.length * BIRDS; i++) {
+        const offset = Math.floor(i / birdGeo.index.array.length) * birdGeo.getAttribute('position').count;
+        indices.push(birdGeo.index.array[i % birdGeo.index.array.length] + offset);
+    }
+
+    BirdGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
+    BirdGeometry.setAttribute('birdColor', new THREE.BufferAttribute(new Float32Array(color), 3));
+    BirdGeometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(color), 3));
+    BirdGeometry.setAttribute('reference', new THREE.BufferAttribute(new Float32Array(reference), 4));
+    BirdGeometry.setAttribute('seeds', new THREE.BufferAttribute(new Float32Array(seeds), 4));
+
+    BirdGeometry.setIndex(indices);
+
+    init();
+});
+
+let container, stats;
+let camera, scene, renderer;
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+const BOUNDS = 800,
+    BOUNDS_HALF = BOUNDS / 2;
+
+let last = performance.now();
+
+let gpuCompute;
+let velocityVariable;
+let positionVariable;
+let positionUniforms;
+let velocityUniforms;
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
+    camera.position.z = 350;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(colors[selectModel]);
+    scene.fog = new THREE.Fog(colors[selectModel], 100, 1000);
+
+    // LIGHTS
+
+    const hemiLight = new THREE.HemisphereLight(colors[selectModel], 0xffffff, 4.5);
+    hemiLight.color.setHSL(0.6, 1, 0.6, THREE.SRGBColorSpace);
+    hemiLight.groundColor.setHSL(0.095, 1, 0.75, THREE.SRGBColorSpace);
+    hemiLight.position.set(0, 50, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0x00ced1, 2.0);
+    dirLight.color.setHSL(0.1, 1, 0.95, THREE.SRGBColorSpace);
+    dirLight.position.set(-1, 1.75, 1);
+    dirLight.position.multiplyScalar(30);
+    scene.add(dirLight);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    initComputeRenderer();
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointermove', onPointerMove);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    const effectController = {
+        separation: 20.0,
+        alignment: 20.0,
+        cohesion: 20.0,
+        freedom: 0.75,
+        size: sizes[selectModel],
+        count: Math.floor(BIRDS / 4),
+    };
+
+    const valuesChanger = function () {
+        velocityUniforms['separationDistance'].value = effectController.separation;
+        velocityUniforms['alignmentDistance'].value = effectController.alignment;
+        velocityUniforms['cohesionDistance'].value = effectController.cohesion;
+        velocityUniforms['freedomFactor'].value = effectController.freedom;
+        if (materialShader) materialShader.uniforms['size'].value = effectController.size;
+        BirdGeometry.setDrawRange(0, indicesPerBird * effectController.count);
+    };
+
+    valuesChanger();
+
+    gui.add(effectController, 'separation', 0.0, 100.0, 1.0).onChange(valuesChanger);
+    gui.add(effectController, 'alignment', 0.0, 100, 0.001).onChange(valuesChanger);
+    gui.add(effectController, 'cohesion', 0.0, 100, 0.025).onChange(valuesChanger);
+    gui.add(effectController, 'size', 0, 1, 0.01).onChange(valuesChanger);
+    gui.add(effectController, 'count', 0, BIRDS, 1).onChange(valuesChanger);
+    gui.close();
+
+    initBirds(effectController);
+}
+
+function initComputeRenderer() {
+    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
+
+    const dtPosition = gpuCompute.createTexture();
+    const dtVelocity = gpuCompute.createTexture();
+    fillPositionTexture(dtPosition);
+    fillVelocityTexture(dtVelocity);
+
+    velocityVariable = gpuCompute.addVariable(
+        'textureVelocity',
+        document.getElementById('fragmentShaderVelocity').textContent,
+        dtVelocity,
+    );
+    positionVariable = gpuCompute.addVariable(
+        'texturePosition',
+        document.getElementById('fragmentShaderPosition').textContent,
+        dtPosition,
+    );
+
+    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
+    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
+
+    positionUniforms = positionVariable.material.uniforms;
+    velocityUniforms = velocityVariable.material.uniforms;
+
+    positionUniforms['time'] = { value: 0.0 };
+    positionUniforms['delta'] = { value: 0.0 };
+    velocityUniforms['time'] = { value: 1.0 };
+    velocityUniforms['delta'] = { value: 0.0 };
+    velocityUniforms['testing'] = { value: 1.0 };
+    velocityUniforms['separationDistance'] = { value: 1.0 };
+    velocityUniforms['alignmentDistance'] = { value: 1.0 };
+    velocityUniforms['cohesionDistance'] = { value: 1.0 };
+    velocityUniforms['freedomFactor'] = { value: 1.0 };
+    velocityUniforms['predator'] = { value: new THREE.Vector3() };
+    velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed(2);
+
+    velocityVariable.wrapS = THREE.RepeatWrapping;
+    velocityVariable.wrapT = THREE.RepeatWrapping;
+    positionVariable.wrapS = THREE.RepeatWrapping;
+    positionVariable.wrapT = THREE.RepeatWrapping;
+
+    const error = gpuCompute.init();
+
+    if (error !== null) {
+        console.error(error);
+    }
+}
+
+function initBirds(effectController) {
+    const geometry = BirdGeometry;
+
+    const m = new THREE.MeshStandardMaterial({
+        vertexColors: true,
+        flatShading: true,
+        roughness: 1,
+        metalness: 0,
+    });
+
+    m.onBeforeCompile = shader => {
+        shader.uniforms.texturePosition = { value: null };
+        shader.uniforms.textureVelocity = { value: null };
+        shader.uniforms.textureAnimation = { value: textureAnimation };
+        shader.uniforms.time = { value: 1.0 };
+        shader.uniforms.size = { value: effectController.size };
+        shader.uniforms.delta = { value: 0.0 };
+
+        let token = '#define STANDARD';
+
+        let insert = /* glsl */ `
+						attribute vec4 reference;
+						attribute vec4 seeds;
+						attribute vec3 birdColor;
+						uniform sampler2D texturePosition;
+						uniform sampler2D textureVelocity;
+						uniform sampler2D textureAnimation;
+						uniform float size;
+						uniform float time;
+					`;
+
+        shader.vertexShader = shader.vertexShader.replace(token, token + insert);
+
+        token = '#include <begin_vertex>';
+
+        insert = /* glsl */ `
+						vec4 tmpPos = texture2D( texturePosition, reference.xy );
+
+						vec3 pos = tmpPos.xyz;
+						vec3 velocity = normalize(texture2D( textureVelocity, reference.xy ).xyz);
+						vec3 aniPos = texture2D( textureAnimation, vec2( reference.z, mod( time + ( seeds.x ) * ( ( 0.0004 + seeds.y / 10000.0) + normalize( velocity ) / 20000.0 ), reference.w ) ) ).xyz;
+						vec3 newPosition = position;
+
+						newPosition = mat3( modelMatrix ) * ( newPosition + aniPos );
+						newPosition *= size + seeds.y * size * 0.2;
+
+						velocity.z *= -1.;
+						float xz = length( velocity.xz );
+						float xyz = 1.;
+						float x = sqrt( 1. - velocity.y * velocity.y );
+
+						float cosry = velocity.x / xz;
+						float sinry = velocity.z / xz;
+
+						float cosrz = x / xyz;
+						float sinrz = velocity.y / xyz;
+
+						mat3 maty =  mat3( cosry, 0, -sinry, 0    , 1, 0     , sinry, 0, cosry );
+						mat3 matz =  mat3( cosrz , sinrz, 0, -sinrz, cosrz, 0, 0     , 0    , 1 );
+
+						newPosition =  maty * matz * newPosition;
+						newPosition += pos;
+
+						vec3 transformed = vec3( newPosition );
+					`;
+
+        shader.vertexShader = shader.vertexShader.replace(token, insert);
+
+        materialShader = shader;
+    };
+
+    birdMesh = new THREE.Mesh(geometry, m);
+    birdMesh.rotation.y = Math.PI / 2;
+
+    birdMesh.castShadow = true;
+    birdMesh.receiveShadow = true;
+
+    scene.add(birdMesh);
+}
+
+function fillPositionTexture(texture) {
+    const theArray = texture.image.data;
+
+    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
+        const x = Math.random() * BOUNDS - BOUNDS_HALF;
+        const y = Math.random() * BOUNDS - BOUNDS_HALF;
+        const z = Math.random() * BOUNDS - BOUNDS_HALF;
+
+        theArray[k + 0] = x;
+        theArray[k + 1] = y;
+        theArray[k + 2] = z;
+        theArray[k + 3] = 1;
+    }
+}
+
+function fillVelocityTexture(texture) {
+    const theArray = texture.image.data;
+
+    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
+        const x = Math.random() - 0.5;
+        const y = Math.random() - 0.5;
+        const z = Math.random() - 0.5;
+
+        theArray[k + 0] = x * 10;
+        theArray[k + 1] = y * 10;
+        theArray[k + 2] = z * 10;
+        theArray[k + 3] = 1;
+    }
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const now = performance.now();
+    let delta = (now - last) / 1000;
+
+    if (delta > 1) delta = 1; // safety cap on large deltas
+    last = now;
+
+    positionUniforms['time'].value = now;
+    positionUniforms['delta'].value = delta;
+    velocityUniforms['time'].value = now;
+    velocityUniforms['delta'].value = delta;
+    if (materialShader) materialShader.uniforms['time'].value = now / 1000;
+    if (materialShader) materialShader.uniforms['delta'].value = delta;
+
+    velocityUniforms['predator'].value.set((0.5 * mouseX) / windowHalfX, (-0.5 * mouseY) / windowHalfY, 0);
+
+    mouseX = 10000;
+    mouseY = 10000;
+
+    gpuCompute.compute();
+
+    if (materialShader)
+        materialShader.uniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
+    if (materialShader)
+        materialShader.uniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_gpgpu_protoplanet.ts b/examples-testing/examples/webgl_gpgpu_protoplanet.ts
new file mode 100644
index 000000000..30444ddba
--- /dev/null
+++ b/examples-testing/examples/webgl_gpgpu_protoplanet.ts
@@ -0,0 +1,280 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
+
+// Texture width for simulation (each texel is a debris particle)
+const WIDTH = 64;
+
+let container, stats;
+let camera, scene, renderer, geometry;
+
+const PARTICLES = WIDTH * WIDTH;
+
+let gpuCompute;
+let velocityVariable;
+let positionVariable;
+let velocityUniforms;
+let particleUniforms;
+let effectController;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 5, 15000);
+    camera.position.y = 120;
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 100;
+    controls.maxDistance = 1000;
+
+    effectController = {
+        // Can be changed dynamically
+        gravityConstant: 100.0,
+        density: 0.45,
+
+        // Must restart simulation
+        radius: 300,
+        height: 8,
+        exponent: 0.4,
+        maxMass: 15.0,
+        velocity: 70,
+        velocityExponent: 0.2,
+        randVelocity: 0.001,
+    };
+
+    initComputeRenderer();
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    initGUI();
+
+    initProtoplanets();
+
+    dynamicValuesChanger();
+}
+
+function initComputeRenderer() {
+    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
+
+    const dtPosition = gpuCompute.createTexture();
+    const dtVelocity = gpuCompute.createTexture();
+
+    fillTextures(dtPosition, dtVelocity);
+
+    velocityVariable = gpuCompute.addVariable(
+        'textureVelocity',
+        document.getElementById('computeShaderVelocity').textContent,
+        dtVelocity,
+    );
+    positionVariable = gpuCompute.addVariable(
+        'texturePosition',
+        document.getElementById('computeShaderPosition').textContent,
+        dtPosition,
+    );
+
+    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
+    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
+
+    velocityUniforms = velocityVariable.material.uniforms;
+
+    velocityUniforms['gravityConstant'] = { value: 0.0 };
+    velocityUniforms['density'] = { value: 0.0 };
+
+    const error = gpuCompute.init();
+
+    if (error !== null) {
+        console.error(error);
+    }
+}
+
+function restartSimulation() {
+    const dtPosition = gpuCompute.createTexture();
+    const dtVelocity = gpuCompute.createTexture();
+
+    fillTextures(dtPosition, dtVelocity);
+
+    gpuCompute.renderTexture(dtPosition, positionVariable.renderTargets[0]);
+    gpuCompute.renderTexture(dtPosition, positionVariable.renderTargets[1]);
+    gpuCompute.renderTexture(dtVelocity, velocityVariable.renderTargets[0]);
+    gpuCompute.renderTexture(dtVelocity, velocityVariable.renderTargets[1]);
+}
+
+function initProtoplanets() {
+    geometry = new THREE.BufferGeometry();
+
+    const positions = new Float32Array(PARTICLES * 3);
+    let p = 0;
+
+    for (let i = 0; i < PARTICLES; i++) {
+        positions[p++] = (Math.random() * 2 - 1) * effectController.radius;
+        positions[p++] = 0; //( Math.random() * 2 - 1 ) * effectController.radius;
+        positions[p++] = (Math.random() * 2 - 1) * effectController.radius;
+    }
+
+    const uvs = new Float32Array(PARTICLES * 2);
+    p = 0;
+
+    for (let j = 0; j < WIDTH; j++) {
+        for (let i = 0; i < WIDTH; i++) {
+            uvs[p++] = i / (WIDTH - 1);
+            uvs[p++] = j / (WIDTH - 1);
+        }
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
+
+    particleUniforms = {
+        texturePosition: { value: null },
+        textureVelocity: { value: null },
+        cameraConstant: { value: getCameraConstant(camera) },
+        density: { value: 0.0 },
+    };
+
+    // THREE.ShaderMaterial
+    const material = new THREE.ShaderMaterial({
+        uniforms: particleUniforms,
+        vertexShader: document.getElementById('particleVertexShader').textContent,
+        fragmentShader: document.getElementById('particleFragmentShader').textContent,
+    });
+
+    const particles = new THREE.Points(geometry, material);
+    particles.matrixAutoUpdate = false;
+    particles.updateMatrix();
+
+    scene.add(particles);
+}
+
+function fillTextures(texturePosition, textureVelocity) {
+    const posArray = texturePosition.image.data;
+    const velArray = textureVelocity.image.data;
+
+    const radius = effectController.radius;
+    const height = effectController.height;
+    const exponent = effectController.exponent;
+    const maxMass = (effectController.maxMass * 1024) / PARTICLES;
+    const maxVel = effectController.velocity;
+    const velExponent = effectController.velocityExponent;
+    const randVel = effectController.randVelocity;
+
+    for (let k = 0, kl = posArray.length; k < kl; k += 4) {
+        // Position
+        let x, z, rr;
+
+        do {
+            x = Math.random() * 2 - 1;
+            z = Math.random() * 2 - 1;
+            rr = x * x + z * z;
+        } while (rr > 1);
+
+        rr = Math.sqrt(rr);
+
+        const rExp = radius * Math.pow(rr, exponent);
+
+        // Velocity
+        const vel = maxVel * Math.pow(rr, velExponent);
+
+        const vx = vel * z + (Math.random() * 2 - 1) * randVel;
+        const vy = (Math.random() * 2 - 1) * randVel * 0.05;
+        const vz = -vel * x + (Math.random() * 2 - 1) * randVel;
+
+        x *= rExp;
+        z *= rExp;
+        const y = (Math.random() * 2 - 1) * height;
+
+        const mass = Math.random() * maxMass + 1;
+
+        // Fill in texture values
+        posArray[k + 0] = x;
+        posArray[k + 1] = y;
+        posArray[k + 2] = z;
+        posArray[k + 3] = 1;
+
+        velArray[k + 0] = vx;
+        velArray[k + 1] = vy;
+        velArray[k + 2] = vz;
+        velArray[k + 3] = mass;
+    }
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    particleUniforms['cameraConstant'].value = getCameraConstant(camera);
+}
+
+function dynamicValuesChanger() {
+    velocityUniforms['gravityConstant'].value = effectController.gravityConstant;
+    velocityUniforms['density'].value = effectController.density;
+    particleUniforms['density'].value = effectController.density;
+}
+
+function initGUI() {
+    const gui = new GUI({ width: 280 });
+
+    const folder1 = gui.addFolder('Dynamic parameters');
+
+    folder1.add(effectController, 'gravityConstant', 0.0, 1000.0, 0.05).onChange(dynamicValuesChanger);
+    folder1.add(effectController, 'density', 0.0, 10.0, 0.001).onChange(dynamicValuesChanger);
+
+    const folder2 = gui.addFolder('Static parameters');
+
+    folder2.add(effectController, 'radius', 10.0, 1000.0, 1.0);
+    folder2.add(effectController, 'height', 0.0, 50.0, 0.01);
+    folder2.add(effectController, 'exponent', 0.0, 2.0, 0.001);
+    folder2.add(effectController, 'maxMass', 1.0, 50.0, 0.1);
+    folder2.add(effectController, 'velocity', 0.0, 150.0, 0.1);
+    folder2.add(effectController, 'velocityExponent', 0.0, 1.0, 0.01);
+    folder2.add(effectController, 'randVelocity', 0.0, 50.0, 0.1);
+
+    const buttonRestart = {
+        restartSimulation: function () {
+            restartSimulation();
+        },
+    };
+
+    folder2.add(buttonRestart, 'restartSimulation');
+
+    folder1.open();
+    folder2.open();
+}
+
+function getCameraConstant(camera) {
+    return window.innerHeight / (Math.tan(THREE.MathUtils.DEG2RAD * 0.5 * camera.fov) / camera.zoom);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    gpuCompute.compute();
+
+    particleUniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
+    particleUniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_gpgpu_water.ts b/examples-testing/examples/webgl_gpgpu_water.ts
new file mode 100644
index 000000000..00c32f229
--- /dev/null
+++ b/examples-testing/examples/webgl_gpgpu_water.ts
@@ -0,0 +1,397 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
+import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
+
+// Texture width for simulation
+const WIDTH = 128;
+
+// Water size in system units
+const BOUNDS = 512;
+const BOUNDS_HALF = BOUNDS * 0.5;
+
+let container, stats;
+let camera, scene, renderer;
+let mouseMoved = false;
+const mouseCoords = new THREE.Vector2();
+const raycaster = new THREE.Raycaster();
+
+let waterMesh;
+let meshRay;
+let gpuCompute;
+let heightmapVariable;
+let waterUniforms;
+let smoothShader;
+let readWaterLevelShader;
+let readWaterLevelRenderTarget;
+let readWaterLevelImage;
+const waterNormal = new THREE.Vector3();
+
+const NUM_SPHERES = 5;
+const spheres = [];
+let spheresEnabled = true;
+
+const simplex = new SimplexNoise();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
+    camera.position.set(0, 200, 350);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+
+    const sun = new THREE.DirectionalLight(0xffffff, 3.0);
+    sun.position.set(300, 400, 175);
+    scene.add(sun);
+
+    const sun2 = new THREE.DirectionalLight(0x40a040, 2.0);
+    sun2.position.set(-100, 350, -200);
+    scene.add(sun2);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointermove', onPointerMove);
+
+    document.addEventListener('keydown', function (event) {
+        // W Pressed: Toggle wireframe
+        if (event.keyCode === 87) {
+            waterMesh.material.wireframe = !waterMesh.material.wireframe;
+            waterMesh.material.needsUpdate = true;
+        }
+    });
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    const effectController = {
+        mouseSize: 20.0,
+        viscosity: 0.98,
+        spheresEnabled: spheresEnabled,
+    };
+
+    const valuesChanger = function () {
+        heightmapVariable.material.uniforms['mouseSize'].value = effectController.mouseSize;
+        heightmapVariable.material.uniforms['viscosityConstant'].value = effectController.viscosity;
+        spheresEnabled = effectController.spheresEnabled;
+        for (let i = 0; i < NUM_SPHERES; i++) {
+            if (spheres[i]) {
+                spheres[i].visible = spheresEnabled;
+            }
+        }
+    };
+
+    gui.add(effectController, 'mouseSize', 1.0, 100.0, 1.0).onChange(valuesChanger);
+    gui.add(effectController, 'viscosity', 0.9, 0.999, 0.001).onChange(valuesChanger);
+    gui.add(effectController, 'spheresEnabled').onChange(valuesChanger);
+    const buttonSmooth = {
+        smoothWater: function () {
+            smoothWater();
+        },
+    };
+    gui.add(buttonSmooth, 'smoothWater');
+
+    initWater();
+
+    createSpheres();
+
+    valuesChanger();
+}
+
+function initWater() {
+    const materialColor = 0x0040c0;
+
+    const geometry = new THREE.PlaneGeometry(BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1);
+
+    // material: make a THREE.ShaderMaterial clone of THREE.MeshPhongMaterial, with customized vertex shader
+    const material = new THREE.ShaderMaterial({
+        uniforms: THREE.UniformsUtils.merge([
+            THREE.ShaderLib['phong'].uniforms,
+            {
+                heightmap: { value: null },
+            },
+        ]),
+        vertexShader: document.getElementById('waterVertexShader').textContent,
+        fragmentShader: THREE.ShaderChunk['meshphong_frag'],
+    });
+
+    material.lights = true;
+
+    // Material attributes from THREE.MeshPhongMaterial
+    // Sets the uniforms with the material values
+    material.uniforms['diffuse'].value = new THREE.Color(materialColor);
+    material.uniforms['specular'].value = new THREE.Color(0x111111);
+    material.uniforms['shininess'].value = Math.max(50, 1e-4);
+    material.uniforms['opacity'].value = material.opacity;
+
+    // Defines
+    material.defines.WIDTH = WIDTH.toFixed(1);
+    material.defines.BOUNDS = BOUNDS.toFixed(1);
+
+    waterUniforms = material.uniforms;
+
+    waterMesh = new THREE.Mesh(geometry, material);
+    waterMesh.rotation.x = -Math.PI / 2;
+    waterMesh.matrixAutoUpdate = false;
+    waterMesh.updateMatrix();
+
+    scene.add(waterMesh);
+
+    // THREE.Mesh just for mouse raycasting
+    const geometryRay = new THREE.PlaneGeometry(BOUNDS, BOUNDS, 1, 1);
+    meshRay = new THREE.Mesh(geometryRay, new THREE.MeshBasicMaterial({ color: 0xffffff, visible: false }));
+    meshRay.rotation.x = -Math.PI / 2;
+    meshRay.matrixAutoUpdate = false;
+    meshRay.updateMatrix();
+    scene.add(meshRay);
+
+    // Creates the gpu computation class and sets it up
+
+    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
+
+    const heightmap0 = gpuCompute.createTexture();
+
+    fillTexture(heightmap0);
+
+    heightmapVariable = gpuCompute.addVariable(
+        'heightmap',
+        document.getElementById('heightmapFragmentShader').textContent,
+        heightmap0,
+    );
+
+    gpuCompute.setVariableDependencies(heightmapVariable, [heightmapVariable]);
+
+    heightmapVariable.material.uniforms['mousePos'] = { value: new THREE.Vector2(10000, 10000) };
+    heightmapVariable.material.uniforms['mouseSize'] = { value: 20.0 };
+    heightmapVariable.material.uniforms['viscosityConstant'] = { value: 0.98 };
+    heightmapVariable.material.uniforms['heightCompensation'] = { value: 0 };
+    heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed(1);
+
+    const error = gpuCompute.init();
+    if (error !== null) {
+        console.error(error);
+    }
+
+    // Create compute shader to smooth the water surface and velocity
+    smoothShader = gpuCompute.createShaderMaterial(document.getElementById('smoothFragmentShader').textContent, {
+        smoothTexture: { value: null },
+    });
+
+    // Create compute shader to read water level
+    readWaterLevelShader = gpuCompute.createShaderMaterial(
+        document.getElementById('readWaterLevelFragmentShader').textContent,
+        {
+            point1: { value: new THREE.Vector2() },
+            levelTexture: { value: null },
+        },
+    );
+    readWaterLevelShader.defines.WIDTH = WIDTH.toFixed(1);
+    readWaterLevelShader.defines.BOUNDS = BOUNDS.toFixed(1);
+
+    // Create a 4x1 pixel image and a render target (Uint8, 4 channels, 1 byte per channel) to read water height and orientation
+    readWaterLevelImage = new Uint8Array(4 * 1 * 4);
+
+    readWaterLevelRenderTarget = new THREE.WebGLRenderTarget(4, 1, {
+        wrapS: THREE.ClampToEdgeWrapping,
+        wrapT: THREE.ClampToEdgeWrapping,
+        minFilter: THREE.NearestFilter,
+        magFilter: THREE.NearestFilter,
+        format: THREE.RGBAFormat,
+        type: THREE.UnsignedByteType,
+        depthBuffer: false,
+    });
+}
+
+function fillTexture(texture) {
+    const waterMaxHeight = 10;
+
+    function noise(x, y) {
+        let multR = waterMaxHeight;
+        let mult = 0.025;
+        let r = 0;
+        for (let i = 0; i < 15; i++) {
+            r += multR * simplex.noise(x * mult, y * mult);
+            multR *= 0.53 + 0.025 * i;
+            mult *= 1.25;
+        }
+
+        return r;
+    }
+
+    const pixels = texture.image.data;
+
+    let p = 0;
+    for (let j = 0; j < WIDTH; j++) {
+        for (let i = 0; i < WIDTH; i++) {
+            const x = (i * 128) / WIDTH;
+            const y = (j * 128) / WIDTH;
+
+            pixels[p + 0] = noise(x, y);
+            pixels[p + 1] = pixels[p + 0];
+            pixels[p + 2] = 0;
+            pixels[p + 3] = 1;
+
+            p += 4;
+        }
+    }
+}
+
+function smoothWater() {
+    const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
+    const alternateRenderTarget = gpuCompute.getAlternateRenderTarget(heightmapVariable);
+
+    for (let i = 0; i < 10; i++) {
+        smoothShader.uniforms['smoothTexture'].value = currentRenderTarget.texture;
+        gpuCompute.doRenderTarget(smoothShader, alternateRenderTarget);
+
+        smoothShader.uniforms['smoothTexture'].value = alternateRenderTarget.texture;
+        gpuCompute.doRenderTarget(smoothShader, currentRenderTarget);
+    }
+}
+
+function createSpheres() {
+    const sphereTemplate = new THREE.Mesh(
+        new THREE.SphereGeometry(4, 24, 12),
+        new THREE.MeshPhongMaterial({ color: 0xffff00 }),
+    );
+
+    for (let i = 0; i < NUM_SPHERES; i++) {
+        let sphere = sphereTemplate;
+        if (i < NUM_SPHERES - 1) {
+            sphere = sphereTemplate.clone();
+        }
+
+        sphere.position.x = (Math.random() - 0.5) * BOUNDS * 0.7;
+        sphere.position.z = (Math.random() - 0.5) * BOUNDS * 0.7;
+
+        sphere.userData.velocity = new THREE.Vector3();
+
+        scene.add(sphere);
+
+        spheres[i] = sphere;
+    }
+}
+
+function sphereDynamics() {
+    const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
+
+    readWaterLevelShader.uniforms['levelTexture'].value = currentRenderTarget.texture;
+
+    for (let i = 0; i < NUM_SPHERES; i++) {
+        const sphere = spheres[i];
+
+        if (sphere) {
+            // Read water level and orientation
+            const u = (0.5 * sphere.position.x) / BOUNDS_HALF + 0.5;
+            const v = 1 - ((0.5 * sphere.position.z) / BOUNDS_HALF + 0.5);
+            readWaterLevelShader.uniforms['point1'].value.set(u, v);
+            gpuCompute.doRenderTarget(readWaterLevelShader, readWaterLevelRenderTarget);
+
+            renderer.readRenderTargetPixels(readWaterLevelRenderTarget, 0, 0, 4, 1, readWaterLevelImage);
+            const pixels = new Float32Array(readWaterLevelImage.buffer);
+
+            // Get orientation
+            waterNormal.set(pixels[1], 0, -pixels[2]);
+
+            const pos = sphere.position;
+
+            // Set height
+            pos.y = pixels[0];
+
+            // Move sphere
+            waterNormal.multiplyScalar(0.1);
+            sphere.userData.velocity.add(waterNormal);
+            sphere.userData.velocity.multiplyScalar(0.998);
+            pos.add(sphere.userData.velocity);
+
+            if (pos.x < -BOUNDS_HALF) {
+                pos.x = -BOUNDS_HALF + 0.001;
+                sphere.userData.velocity.x *= -0.3;
+            } else if (pos.x > BOUNDS_HALF) {
+                pos.x = BOUNDS_HALF - 0.001;
+                sphere.userData.velocity.x *= -0.3;
+            }
+
+            if (pos.z < -BOUNDS_HALF) {
+                pos.z = -BOUNDS_HALF + 0.001;
+                sphere.userData.velocity.z *= -0.3;
+            } else if (pos.z > BOUNDS_HALF) {
+                pos.z = BOUNDS_HALF - 0.001;
+                sphere.userData.velocity.z *= -0.3;
+            }
+        }
+    }
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function setMouseCoords(x, y) {
+    mouseCoords.set((x / renderer.domElement.clientWidth) * 2 - 1, -(y / renderer.domElement.clientHeight) * 2 + 1);
+    mouseMoved = true;
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    setMouseCoords(event.clientX, event.clientY);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    // Set uniforms: mouse interaction
+    const uniforms = heightmapVariable.material.uniforms;
+    if (mouseMoved) {
+        raycaster.setFromCamera(mouseCoords, camera);
+
+        const intersects = raycaster.intersectObject(meshRay);
+
+        if (intersects.length > 0) {
+            const point = intersects[0].point;
+            uniforms['mousePos'].value.set(point.x, point.z);
+        } else {
+            uniforms['mousePos'].value.set(10000, 10000);
+        }
+
+        mouseMoved = false;
+    } else {
+        uniforms['mousePos'].value.set(10000, 10000);
+    }
+
+    // Do the gpu computation
+    gpuCompute.compute();
+
+    if (spheresEnabled) {
+        sphereDynamics();
+    }
+
+    // Get compute output in custom uniform
+    waterUniforms['heightmap'].value = gpuCompute.getCurrentRenderTarget(heightmapVariable).texture;
+
+    // Render
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_helpers.ts b/examples-testing/examples/webgl_helpers.ts
new file mode 100644
index 000000000..a8c3b9773
--- /dev/null
+++ b/examples-testing/examples/webgl_helpers.ts
@@ -0,0 +1,117 @@
+import * as THREE from 'three';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { VertexNormalsHelper } from 'three/addons/helpers/VertexNormalsHelper.js';
+import { VertexTangentsHelper } from 'three/addons/helpers/VertexTangentsHelper.js';
+
+let scene, renderer;
+let camera, light;
+let vnh;
+let vth;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+
+    light = new THREE.PointLight();
+    light.position.set(200, 100, 150);
+    scene.add(light);
+
+    scene.add(new THREE.PointLightHelper(light, 15));
+
+    const gridHelper = new THREE.GridHelper(400, 40, 0x0000ff, 0x808080);
+    gridHelper.position.y = -150;
+    gridHelper.position.x = -150;
+    scene.add(gridHelper);
+
+    const polarGridHelper = new THREE.PolarGridHelper(200, 16, 8, 64, 0x0000ff, 0x808080);
+    polarGridHelper.position.y = -150;
+    polarGridHelper.position.x = 200;
+    scene.add(polarGridHelper);
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+
+        mesh.geometry.computeTangents(); // generates bad data due to degenerate UVs
+
+        const group = new THREE.Group();
+        group.scale.multiplyScalar(50);
+        scene.add(group);
+
+        // To make sure that the matrixWorld is up to date for the boxhelpers
+        group.updateMatrixWorld(true);
+
+        group.add(mesh);
+
+        vnh = new VertexNormalsHelper(mesh, 5);
+        scene.add(vnh);
+
+        vth = new VertexTangentsHelper(mesh, 5);
+        scene.add(vth);
+
+        scene.add(new THREE.BoxHelper(mesh));
+
+        const wireframe = new THREE.WireframeGeometry(mesh.geometry);
+        let line = new THREE.LineSegments(wireframe);
+        line.material.depthTest = false;
+        line.material.opacity = 0.25;
+        line.material.transparent = true;
+        line.position.x = 4;
+        group.add(line);
+        scene.add(new THREE.BoxHelper(line));
+
+        const edges = new THREE.EdgesGeometry(mesh.geometry);
+        line = new THREE.LineSegments(edges);
+        line.material.depthTest = false;
+        line.material.opacity = 0.25;
+        line.material.transparent = true;
+        line.position.x = -4;
+        group.add(line);
+        scene.add(new THREE.BoxHelper(line));
+
+        scene.add(new THREE.BoxHelper(group));
+        scene.add(new THREE.BoxHelper(scene));
+    });
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = -performance.now() * 0.0003;
+
+    camera.position.x = 400 * Math.cos(time);
+    camera.position.z = 400 * Math.sin(time);
+    camera.lookAt(scene.position);
+
+    light.position.x = Math.sin(time * 1.7) * 300;
+    light.position.y = Math.cos(time * 1.5) * 400;
+    light.position.z = Math.cos(time * 1.3) * 300;
+
+    if (vnh) vnh.update();
+    if (vth) vth.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_instancing_dynamic.ts b/examples-testing/examples/webgl_instancing_dynamic.ts
new file mode 100644
index 000000000..88562fc5a
--- /dev/null
+++ b/examples-testing/examples/webgl_instancing_dynamic.ts
@@ -0,0 +1,103 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats;
+
+let mesh;
+const amount = parseInt(window.location.search.slice(1)) || 10;
+const count = Math.pow(amount, 3);
+const dummy = new THREE.Object3D();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(amount * 0.9, amount * 0.9, amount * 0.9);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+
+    const loader = new THREE.BufferGeometryLoader();
+    loader.load('models/json/suzanne_buffergeometry.json', function (geometry) {
+        geometry.computeVertexNormals();
+        geometry.scale(0.5, 0.5, 0.5);
+
+        const material = new THREE.MeshNormalMaterial();
+        // check overdraw
+        // let material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } );
+
+        mesh = new THREE.InstancedMesh(geometry, material, count);
+        mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
+        scene.add(mesh);
+
+        //
+
+        const gui = new GUI();
+        gui.add(mesh, 'count', 0, count);
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    if (mesh) {
+        const time = Date.now() * 0.001;
+
+        mesh.rotation.x = Math.sin(time / 4);
+        mesh.rotation.y = Math.sin(time / 2);
+
+        let i = 0;
+        const offset = (amount - 1) / 2;
+
+        for (let x = 0; x < amount; x++) {
+            for (let y = 0; y < amount; y++) {
+                for (let z = 0; z < amount; z++) {
+                    dummy.position.set(offset - x, offset - y, offset - z);
+                    dummy.rotation.y = Math.sin(x / 4 + time) + Math.sin(y / 4 + time) + Math.sin(z / 4 + time);
+                    dummy.rotation.z = dummy.rotation.y * 2;
+
+                    dummy.updateMatrix();
+
+                    mesh.setMatrixAt(i++, dummy.matrix);
+                }
+            }
+        }
+
+        mesh.instanceMatrix.needsUpdate = true;
+        mesh.computeBoundingSphere();
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_instancing_morph.ts b/examples-testing/examples/webgl_instancing_morph.ts
new file mode 100644
index 000000000..8686a75b9
--- /dev/null
+++ b/examples-testing/examples/webgl_instancing_morph.ts
@@ -0,0 +1,147 @@
+import * as THREE from 'three';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats, mesh, mixer, dummy;
+
+const offset = 5000;
+
+const timeOffsets = new Float32Array(1024);
+
+for (let i = 0; i < 1024; i++) {
+    timeOffsets[i] = Math.random() * 3;
+}
+
+const clock = new THREE.Clock(true);
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 10000);
+
+    scene = new THREE.Scene();
+
+    scene.background = new THREE.Color(0x99ddff);
+
+    scene.fog = new THREE.Fog(0x99ddff, 5000, 10000);
+
+    const light = new THREE.DirectionalLight(0xffffff, 1);
+
+    light.position.set(200, 1000, 50);
+
+    light.castShadow = true;
+
+    light.shadow.camera.left = -5000;
+    light.shadow.camera.right = 5000;
+    light.shadow.camera.top = 5000;
+    light.shadow.camera.bottom = -5000;
+    light.shadow.camera.far = 2000;
+
+    light.shadow.bias = -0.01;
+
+    light.shadow.camera.updateProjectionMatrix();
+
+    scene.add(light);
+
+    const hemi = new THREE.HemisphereLight(0x99ddff, 0x669933, 1 / 3);
+
+    scene.add(hemi);
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(1000000, 1000000),
+        new THREE.MeshStandardMaterial({ color: 0x669933, depthWrite: true }),
+    );
+
+    ground.rotation.x = -Math.PI / 2;
+
+    ground.receiveShadow = true;
+
+    scene.add(ground);
+
+    const loader = new GLTFLoader();
+
+    loader.load('models/gltf/Horse.glb', function (glb) {
+        dummy = glb.scene.children[0];
+
+        mesh = new THREE.InstancedMesh(dummy.geometry, dummy.material, 1024);
+
+        mesh.castShadow = true;
+
+        for (let x = 0, i = 0; x < 32; x++) {
+            for (let y = 0; y < 32; y++) {
+                dummy.position.set(offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y);
+
+                dummy.updateMatrix();
+
+                mesh.setMatrixAt(i, dummy.matrix);
+
+                mesh.setColorAt(i, new THREE.Color(`hsl(${Math.random() * 360}, 50%, 66%)`));
+
+                i++;
+            }
+        }
+
+        scene.add(mesh);
+
+        mixer = new THREE.AnimationMixer(glb.scene);
+
+        const action = mixer.clipAction(glb.animations[0]);
+
+        action.play();
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.VSMShadowMap;
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const time = clock.getElapsedTime();
+
+    const r = 3000;
+    camera.position.set(Math.sin(time / 10) * r, 1500 + 1000 * Math.cos(time / 5), Math.cos(time / 10) * r);
+    camera.lookAt(0, 0, 0);
+
+    if (mesh) {
+        for (let i = 0; i < 1024; i++) {
+            mixer.setTime(time + timeOffsets[i]);
+
+            mesh.setMorphAt(i, dummy);
+        }
+
+        mesh.morphTexture.needsUpdate = true;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_instancing_performance.ts b/examples-testing/examples/webgl_instancing_performance.ts
new file mode 100644
index 000000000..bf1deabad
--- /dev/null
+++ b/examples-testing/examples/webgl_instancing_performance.ts
@@ -0,0 +1,262 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let container, stats, gui, guiStatsEl;
+let camera, controls, scene, renderer, material;
+
+// gui
+
+const Method = {
+    INSTANCED: 'INSTANCED',
+    MERGED: 'MERGED',
+    NAIVE: 'NAIVE',
+};
+
+const api = {
+    method: Method.INSTANCED,
+    count: 1000,
+};
+
+//
+
+init();
+initMesh();
+
+//
+
+function clean() {
+    const meshes = [];
+
+    scene.traverse(function (object) {
+        if (object.isMesh) meshes.push(object);
+    });
+
+    for (let i = 0; i < meshes.length; i++) {
+        const mesh = meshes[i];
+        mesh.material.dispose();
+        mesh.geometry.dispose();
+
+        scene.remove(mesh);
+    }
+}
+
+const randomizeMatrix = (function () {
+    const position = new THREE.Vector3();
+    const quaternion = new THREE.Quaternion();
+    const scale = new THREE.Vector3();
+
+    return function (matrix) {
+        position.x = Math.random() * 40 - 20;
+        position.y = Math.random() * 40 - 20;
+        position.z = Math.random() * 40 - 20;
+
+        quaternion.random();
+
+        scale.x = scale.y = scale.z = Math.random() * 1;
+
+        matrix.compose(position, quaternion, scale);
+    };
+})();
+
+function initMesh() {
+    clean();
+
+    // make instances
+    new THREE.BufferGeometryLoader().setPath('models/json/').load('suzanne_buffergeometry.json', function (geometry) {
+        material = new THREE.MeshNormalMaterial();
+
+        geometry.computeVertexNormals();
+
+        console.time(api.method + ' (build)');
+
+        switch (api.method) {
+            case Method.INSTANCED:
+                makeInstanced(geometry);
+                break;
+
+            case Method.MERGED:
+                makeMerged(geometry);
+                break;
+
+            case Method.NAIVE:
+                makeNaive(geometry);
+                break;
+        }
+
+        console.timeEnd(api.method + ' (build)');
+    });
+}
+
+function makeInstanced(geometry) {
+    const matrix = new THREE.Matrix4();
+    const mesh = new THREE.InstancedMesh(geometry, material, api.count);
+
+    for (let i = 0; i < api.count; i++) {
+        randomizeMatrix(matrix);
+        mesh.setMatrixAt(i, matrix);
+    }
+
+    scene.add(mesh);
+
+    //
+
+    const geometryByteLength = getGeometryByteLength(geometry);
+
+    guiStatsEl.innerHTML = [
+        '<i>GPU draw calls</i>: 1',
+        '<i>GPU memory</i>: ' + formatBytes(api.count * 16 + geometryByteLength, 2),
+    ].join('<br/>');
+}
+
+function makeMerged(geometry) {
+    const geometries = [];
+    const matrix = new THREE.Matrix4();
+
+    for (let i = 0; i < api.count; i++) {
+        randomizeMatrix(matrix);
+
+        const instanceGeometry = geometry.clone();
+        instanceGeometry.applyMatrix4(matrix);
+
+        geometries.push(instanceGeometry);
+    }
+
+    const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
+
+    scene.add(new THREE.Mesh(mergedGeometry, material));
+
+    //
+
+    guiStatsEl.innerHTML = [
+        '<i>GPU draw calls</i>: 1',
+        '<i>GPU memory</i>: ' + formatBytes(getGeometryByteLength(mergedGeometry), 2),
+    ].join('<br/>');
+}
+
+function makeNaive(geometry) {
+    const matrix = new THREE.Matrix4();
+
+    for (let i = 0; i < api.count; i++) {
+        randomizeMatrix(matrix);
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.applyMatrix4(matrix);
+
+        scene.add(mesh);
+    }
+
+    //
+
+    const geometryByteLength = getGeometryByteLength(geometry);
+
+    guiStatsEl.innerHTML = [
+        '<i>GPU draw calls</i>: ' + api.count,
+        '<i>GPU memory</i>: ' + formatBytes(api.count * 16 + geometryByteLength, 2),
+    ].join('<br/>');
+}
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
+    camera.position.z = 30;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+
+    // stats
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // gui
+
+    gui = new GUI();
+    gui.add(api, 'method', Method).onChange(initMesh);
+    gui.add(api, 'count', 1, 10000).step(1).onChange(initMesh);
+
+    const perfFolder = gui.addFolder('Performance');
+
+    guiStatsEl = document.createElement('div');
+    guiStatsEl.classList.add('gui-stats');
+
+    perfFolder.$children.appendChild(guiStatsEl);
+    perfFolder.open();
+
+    // listeners
+
+    window.addEventListener('resize', onWindowResize);
+
+    Object.assign(window, { scene });
+}
+
+//
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
+
+//
+
+function getGeometryByteLength(geometry) {
+    let total = 0;
+
+    if (geometry.index) total += geometry.index.array.byteLength;
+
+    for (const name in geometry.attributes) {
+        total += geometry.attributes[name].array.byteLength;
+    }
+
+    return total;
+}
+
+// Source: https://stackoverflow.com/a/18650828/1314762
+function formatBytes(bytes, decimals) {
+    if (bytes === 0) return '0 bytes';
+
+    const k = 1024;
+    const dm = decimals < 0 ? 0 : decimals;
+    const sizes = ['bytes', 'KB', 'MB'];
+
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
diff --git a/examples-testing/examples/webgl_instancing_raycast.ts b/examples-testing/examples/webgl_instancing_raycast.ts
new file mode 100644
index 000000000..371ea070b
--- /dev/null
+++ b/examples-testing/examples/webgl_instancing_raycast.ts
@@ -0,0 +1,116 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, controls, stats;
+
+let mesh;
+const amount = parseInt(window.location.search.slice(1)) || 10;
+const count = Math.pow(amount, 3);
+
+const raycaster = new THREE.Raycaster();
+const mouse = new THREE.Vector2(1, 1);
+
+const color = new THREE.Color();
+const white = new THREE.Color().setHex(0xffffff);
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(amount, amount, amount);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+
+    const light = new THREE.HemisphereLight(0xffffff, 0x888888, 3);
+    light.position.set(0, 1, 0);
+    scene.add(light);
+
+    const geometry = new THREE.IcosahedronGeometry(0.5, 3);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff });
+
+    mesh = new THREE.InstancedMesh(geometry, material, count);
+
+    let i = 0;
+    const offset = (amount - 1) / 2;
+
+    const matrix = new THREE.Matrix4();
+
+    for (let x = 0; x < amount; x++) {
+        for (let y = 0; y < amount; y++) {
+            for (let z = 0; z < amount; z++) {
+                matrix.setPosition(offset - x, offset - y, offset - z);
+
+                mesh.setMatrixAt(i, matrix);
+                mesh.setColorAt(i, color);
+
+                i++;
+            }
+        }
+    }
+
+    scene.add(mesh);
+
+    //
+
+    const gui = new GUI();
+    gui.add(mesh, 'count', 0, count);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.enableZoom = false;
+    controls.enablePan = false;
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('mousemove', onMouseMove);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onMouseMove(event) {
+    event.preventDefault();
+
+    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+function animate() {
+    controls.update();
+
+    raycaster.setFromCamera(mouse, camera);
+
+    const intersection = raycaster.intersectObject(mesh);
+
+    if (intersection.length > 0) {
+        const instanceId = intersection[0].instanceId;
+
+        mesh.getColorAt(instanceId, color);
+
+        if (color.equals(white)) {
+            mesh.setColorAt(instanceId, color.setHex(Math.random() * 0xffffff));
+
+            mesh.instanceColor.needsUpdate = true;
+        }
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_instancing_scatter.ts b/examples-testing/examples/webgl_instancing_scatter.ts
new file mode 100644
index 000000000..fc3b9cc9f
--- /dev/null
+++ b/examples-testing/examples/webgl_instancing_scatter.ts
@@ -0,0 +1,257 @@
+import * as THREE from 'three';
+
+import { MeshSurfaceSampler } from 'three/addons/math/MeshSurfaceSampler.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats;
+
+const api = {
+    count: 2000,
+    distribution: 'random',
+    resample: resample,
+    surfaceColor: 0xfff784,
+    backgroundColor: 0xe39469,
+};
+
+let stemMesh, blossomMesh;
+let stemGeometry, blossomGeometry;
+let stemMaterial, blossomMaterial;
+
+let sampler;
+const count = api.count;
+const ages = new Float32Array(count);
+const scales = new Float32Array(count);
+const dummy = new THREE.Object3D();
+
+const _position = new THREE.Vector3();
+const _normal = new THREE.Vector3();
+const _scale = new THREE.Vector3();
+
+// let surfaceGeometry = new THREE.BoxGeometry( 10, 10, 10 ).toNonIndexed();
+const surfaceGeometry = new THREE.TorusKnotGeometry(10, 3, 100, 16).toNonIndexed();
+const surfaceMaterial = new THREE.MeshLambertMaterial({ color: api.surfaceColor, wireframe: false });
+const surface = new THREE.Mesh(surfaceGeometry, surfaceMaterial);
+
+// Source: https://gist.github.com/gre/1650294
+const easeOutCubic = function (t) {
+    return --t * t * t + 1;
+};
+
+// Scaling curve causes particles to grow quickly, ease gradually into full scale, then
+// disappear quickly. More of the particle's lifetime is spent around full scale.
+const scaleCurve = function (t) {
+    return Math.abs(easeOutCubic((t > 0.5 ? 1 - t : t) * 2));
+};
+
+const loader = new GLTFLoader();
+
+loader.load('./models/gltf/Flower/Flower.glb', function (gltf) {
+    const _stemMesh = gltf.scene.getObjectByName('Stem');
+    const _blossomMesh = gltf.scene.getObjectByName('Blossom');
+
+    stemGeometry = _stemMesh.geometry.clone();
+    blossomGeometry = _blossomMesh.geometry.clone();
+
+    const defaultTransform = new THREE.Matrix4()
+        .makeRotationX(Math.PI)
+        .multiply(new THREE.Matrix4().makeScale(7, 7, 7));
+
+    stemGeometry.applyMatrix4(defaultTransform);
+    blossomGeometry.applyMatrix4(defaultTransform);
+
+    stemMaterial = _stemMesh.material;
+    blossomMaterial = _blossomMesh.material;
+
+    stemMesh = new THREE.InstancedMesh(stemGeometry, stemMaterial, count);
+    blossomMesh = new THREE.InstancedMesh(blossomGeometry, blossomMaterial, count);
+
+    // Assign random colors to the blossoms.
+    const color = new THREE.Color();
+    const blossomPalette = [0xf20587, 0xf2d479, 0xf2c879, 0xf2b077, 0xf24405];
+
+    for (let i = 0; i < count; i++) {
+        color.setHex(blossomPalette[Math.floor(Math.random() * blossomPalette.length)]);
+        blossomMesh.setColorAt(i, color);
+    }
+
+    // Instance matrices will be updated every frame.
+    stemMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
+    blossomMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
+
+    resample();
+
+    init();
+});
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(25, 25, 25);
+    camera.lookAt(0, 0, 0);
+
+    //
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(api.backgroundColor);
+
+    const pointLight = new THREE.PointLight(0xaa8899, 2.5, 0, 0);
+    pointLight.position.set(50, -25, 75);
+    scene.add(pointLight);
+
+    scene.add(new THREE.AmbientLight(0xffffff, 3));
+
+    //
+
+    scene.add(stemMesh);
+    scene.add(blossomMesh);
+
+    scene.add(surface);
+
+    //
+
+    const gui = new GUI();
+    gui.add(api, 'count', 0, count).onChange(function () {
+        stemMesh.count = api.count;
+        blossomMesh.count = api.count;
+    });
+
+    // gui.addColor( api, 'backgroundColor' ).onChange( function () {
+
+    // 	scene.background.setHex( api.backgroundColor );
+
+    // } );
+
+    // gui.addColor( api, 'surfaceColor' ).onChange( function () {
+
+    // 	surfaceMaterial.color.setHex( api.surfaceColor );
+
+    // } );
+
+    gui.add(api, 'distribution').options(['random', 'weighted']).onChange(resample);
+    gui.add(api, 'resample');
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function resample() {
+    const vertexCount = surface.geometry.getAttribute('position').count;
+
+    console.info('Sampling ' + count + ' points from a surface with ' + vertexCount + ' vertices...');
+
+    //
+
+    console.time('.build()');
+
+    sampler = new MeshSurfaceSampler(surface).setWeightAttribute(api.distribution === 'weighted' ? 'uv' : null).build();
+
+    console.timeEnd('.build()');
+
+    //
+
+    console.time('.sample()');
+
+    for (let i = 0; i < count; i++) {
+        ages[i] = Math.random();
+        scales[i] = scaleCurve(ages[i]);
+
+        resampleParticle(i);
+    }
+
+    console.timeEnd('.sample()');
+
+    stemMesh.instanceMatrix.needsUpdate = true;
+    blossomMesh.instanceMatrix.needsUpdate = true;
+}
+
+function resampleParticle(i) {
+    sampler.sample(_position, _normal);
+    _normal.add(_position);
+
+    dummy.position.copy(_position);
+    dummy.scale.set(scales[i], scales[i], scales[i]);
+    dummy.lookAt(_normal);
+    dummy.updateMatrix();
+
+    stemMesh.setMatrixAt(i, dummy.matrix);
+    blossomMesh.setMatrixAt(i, dummy.matrix);
+}
+
+function updateParticle(i) {
+    // Update lifecycle.
+
+    ages[i] += 0.005;
+
+    if (ages[i] >= 1) {
+        ages[i] = 0.001;
+        scales[i] = scaleCurve(ages[i]);
+
+        resampleParticle(i);
+
+        return;
+    }
+
+    // Update scale.
+
+    const prevScale = scales[i];
+    scales[i] = scaleCurve(ages[i]);
+    _scale.set(scales[i] / prevScale, scales[i] / prevScale, scales[i] / prevScale);
+
+    // Update transform.
+
+    stemMesh.getMatrixAt(i, dummy.matrix);
+    dummy.matrix.scale(_scale);
+    stemMesh.setMatrixAt(i, dummy.matrix);
+    blossomMesh.setMatrixAt(i, dummy.matrix);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    if (stemMesh && blossomMesh) {
+        const time = Date.now() * 0.001;
+
+        scene.rotation.x = Math.sin(time / 4);
+        scene.rotation.y = Math.sin(time / 2);
+
+        for (let i = 0; i < api.count; i++) {
+            updateParticle(i);
+        }
+
+        stemMesh.instanceMatrix.needsUpdate = true;
+        blossomMesh.instanceMatrix.needsUpdate = true;
+
+        stemMesh.computeBoundingSphere();
+        blossomMesh.computeBoundingSphere();
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_buffergeometry.ts b/examples-testing/examples/webgl_interactive_buffergeometry.ts
new file mode 100644
index 000000000..1d6608b13
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_buffergeometry.ts
@@ -0,0 +1,244 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let raycaster, pointer;
+
+let mesh, line;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
+    camera.position.z = 2750;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
+
+    //
+
+    scene.add(new THREE.AmbientLight(0x444444, 3));
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
+    light1.position.set(1, 1, 1);
+    scene.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
+    light2.position.set(0, -1, 0);
+    scene.add(light2);
+
+    //
+
+    const triangles = 5000;
+
+    let geometry = new THREE.BufferGeometry();
+
+    const positions = new Float32Array(triangles * 3 * 3);
+    const normals = new Float32Array(triangles * 3 * 3);
+    const colors = new Float32Array(triangles * 3 * 3);
+
+    const color = new THREE.Color();
+
+    const n = 800,
+        n2 = n / 2; // triangles spread in the cube
+    const d = 120,
+        d2 = d / 2; // individual triangle size
+
+    const pA = new THREE.Vector3();
+    const pB = new THREE.Vector3();
+    const pC = new THREE.Vector3();
+
+    const cb = new THREE.Vector3();
+    const ab = new THREE.Vector3();
+
+    for (let i = 0; i < positions.length; i += 9) {
+        // positions
+
+        const x = Math.random() * n - n2;
+        const y = Math.random() * n - n2;
+        const z = Math.random() * n - n2;
+
+        const ax = x + Math.random() * d - d2;
+        const ay = y + Math.random() * d - d2;
+        const az = z + Math.random() * d - d2;
+
+        const bx = x + Math.random() * d - d2;
+        const by = y + Math.random() * d - d2;
+        const bz = z + Math.random() * d - d2;
+
+        const cx = x + Math.random() * d - d2;
+        const cy = y + Math.random() * d - d2;
+        const cz = z + Math.random() * d - d2;
+
+        positions[i] = ax;
+        positions[i + 1] = ay;
+        positions[i + 2] = az;
+
+        positions[i + 3] = bx;
+        positions[i + 4] = by;
+        positions[i + 5] = bz;
+
+        positions[i + 6] = cx;
+        positions[i + 7] = cy;
+        positions[i + 8] = cz;
+
+        // flat face normals
+
+        pA.set(ax, ay, az);
+        pB.set(bx, by, bz);
+        pC.set(cx, cy, cz);
+
+        cb.subVectors(pC, pB);
+        ab.subVectors(pA, pB);
+        cb.cross(ab);
+
+        cb.normalize();
+
+        const nx = cb.x;
+        const ny = cb.y;
+        const nz = cb.z;
+
+        normals[i] = nx;
+        normals[i + 1] = ny;
+        normals[i + 2] = nz;
+
+        normals[i + 3] = nx;
+        normals[i + 4] = ny;
+        normals[i + 5] = nz;
+
+        normals[i + 6] = nx;
+        normals[i + 7] = ny;
+        normals[i + 8] = nz;
+
+        // colors
+
+        const vx = x / n + 0.5;
+        const vy = y / n + 0.5;
+        const vz = z / n + 0.5;
+
+        color.setRGB(vx, vy, vz);
+
+        colors[i] = color.r;
+        colors[i + 1] = color.g;
+        colors[i + 2] = color.b;
+
+        colors[i + 3] = color.r;
+        colors[i + 4] = color.g;
+        colors[i + 5] = color.b;
+
+        colors[i + 6] = color.r;
+        colors[i + 7] = color.g;
+        colors[i + 8] = color.b;
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
+    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
+
+    geometry.computeBoundingSphere();
+
+    let material = new THREE.MeshPhongMaterial({
+        color: 0xaaaaaa,
+        specular: 0xffffff,
+        shininess: 250,
+        side: THREE.DoubleSide,
+        vertexColors: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    raycaster = new THREE.Raycaster();
+
+    pointer = new THREE.Vector2();
+
+    geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(4 * 3), 3));
+
+    material = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true });
+
+    line = new THREE.Line(geometry, material);
+    scene.add(line);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('pointermove', onPointerMove);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.001;
+
+    mesh.rotation.x = time * 0.15;
+    mesh.rotation.y = time * 0.25;
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObject(mesh);
+
+    if (intersects.length > 0) {
+        const intersect = intersects[0];
+        const face = intersect.face;
+
+        const linePosition = line.geometry.attributes.position;
+        const meshPosition = mesh.geometry.attributes.position;
+
+        linePosition.copyAt(0, meshPosition, face.a);
+        linePosition.copyAt(1, meshPosition, face.b);
+        linePosition.copyAt(2, meshPosition, face.c);
+        linePosition.copyAt(3, meshPosition, face.a);
+
+        mesh.updateMatrix();
+
+        line.geometry.applyMatrix4(mesh.matrix);
+
+        line.visible = true;
+    } else {
+        line.visible = false;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_cubes.ts b/examples-testing/examples/webgl_interactive_cubes.ts
new file mode 100644
index 000000000..adfcfddf8
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_cubes.ts
@@ -0,0 +1,114 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let stats;
+let camera, scene, raycaster, renderer;
+
+let INTERSECTED;
+let theta = 0;
+
+const pointer = new THREE.Vector2();
+const radius = 5;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1).normalize();
+    scene.add(light);
+
+    const geometry = new THREE.BoxGeometry();
+
+    for (let i = 0; i < 2000; i++) {
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
+
+        object.position.x = Math.random() * 40 - 20;
+        object.position.y = Math.random() * 40 - 20;
+        object.position.z = Math.random() * 40 - 20;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        object.scale.z = Math.random() + 0.5;
+
+        scene.add(object);
+    }
+
+    raycaster = new THREE.Raycaster();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    theta += 0.1;
+
+    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
+    camera.lookAt(scene.position);
+
+    camera.updateMatrixWorld();
+
+    // find intersections
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObjects(scene.children, false);
+
+    if (intersects.length > 0) {
+        if (INTERSECTED != intersects[0].object) {
+            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
+
+            INTERSECTED = intersects[0].object;
+            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
+            INTERSECTED.material.emissive.setHex(0xff0000);
+        }
+    } else {
+        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
+
+        INTERSECTED = null;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_cubes_gpu.ts b/examples-testing/examples/webgl_interactive_cubes_gpu.ts
new file mode 100644
index 000000000..2644469c3
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_cubes_gpu.ts
@@ -0,0 +1,229 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let container, stats;
+let camera, controls, scene, renderer;
+let pickingTexture, pickingScene;
+let highlightBox;
+
+const pickingData = [];
+
+const pointer = new THREE.Vector2();
+const offset = new THREE.Vector3(10, 10, 10);
+const clearColor = new THREE.Color();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 500, 2000);
+    scene.add(light);
+
+    const defaultMaterial = new THREE.MeshPhongMaterial({
+        color: 0xffffff,
+        flatShading: true,
+        vertexColors: true,
+        shininess: 0,
+    });
+
+    // set up the picking texture to use a 32 bit integer so we can write and read integer ids from it
+    pickingScene = new THREE.Scene();
+    pickingTexture = new THREE.WebGLRenderTarget(1, 1, {
+        type: THREE.IntType,
+        format: THREE.RGBAIntegerFormat,
+        internalFormat: 'RGBA32I',
+    });
+    const pickingMaterial = new THREE.ShaderMaterial({
+        glslVersion: THREE.GLSL3,
+
+        vertexShader: /* glsl */ `
+						attribute int id;
+						flat varying int vid;
+						void main() {
+
+							vid = id;
+							gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+						}
+					`,
+
+        fragmentShader: /* glsl */ `
+						layout(location = 0) out int out_id;
+						flat varying int vid;
+
+						void main() {
+
+							out_id = vid;
+
+						}
+					`,
+    });
+
+    function applyId(geometry, id) {
+        const position = geometry.attributes.position;
+        const array = new Int16Array(position.count);
+        array.fill(id);
+
+        const bufferAttribute = new THREE.Int16BufferAttribute(array, 1, false);
+        bufferAttribute.gpuType = THREE.IntType;
+        geometry.setAttribute('id', bufferAttribute);
+    }
+
+    function applyVertexColors(geometry, color) {
+        const position = geometry.attributes.position;
+        const colors = [];
+
+        for (let i = 0; i < position.count; i++) {
+            colors.push(color.r, color.g, color.b);
+        }
+
+        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+    }
+
+    const geometries = [];
+    const matrix = new THREE.Matrix4();
+    const quaternion = new THREE.Quaternion();
+    const color = new THREE.Color();
+
+    for (let i = 0; i < 5000; i++) {
+        const geometry = new THREE.BoxGeometry();
+
+        const position = new THREE.Vector3();
+        position.x = Math.random() * 10000 - 5000;
+        position.y = Math.random() * 6000 - 3000;
+        position.z = Math.random() * 8000 - 4000;
+
+        const rotation = new THREE.Euler();
+        rotation.x = Math.random() * 2 * Math.PI;
+        rotation.y = Math.random() * 2 * Math.PI;
+        rotation.z = Math.random() * 2 * Math.PI;
+
+        const scale = new THREE.Vector3();
+        scale.x = Math.random() * 200 + 100;
+        scale.y = Math.random() * 200 + 100;
+        scale.z = Math.random() * 200 + 100;
+
+        quaternion.setFromEuler(rotation);
+        matrix.compose(position, quaternion, scale);
+
+        geometry.applyMatrix4(matrix);
+
+        // give the geometry's vertices a random color to be displayed and an integer
+        // identifier as a vertex attribute so boxes can be identified after being merged.
+        applyVertexColors(geometry, color.setHex(Math.random() * 0xffffff));
+        applyId(geometry, i);
+
+        geometries.push(geometry);
+
+        pickingData[i] = {
+            position: position,
+            rotation: rotation,
+            scale: scale,
+        };
+    }
+
+    const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
+    scene.add(new THREE.Mesh(mergedGeometry, defaultMaterial));
+    pickingScene.add(new THREE.Mesh(mergedGeometry, pickingMaterial));
+
+    highlightBox = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshLambertMaterial({ color: 0xffff00 }));
+    scene.add(highlightBox);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.rotateSpeed = 1.0;
+    controls.zoomSpeed = 1.2;
+    controls.panSpeed = 0.8;
+    controls.noZoom = false;
+    controls.noPan = false;
+    controls.staticMoving = true;
+    controls.dynamicDampingFactor = 0.3;
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    renderer.domElement.addEventListener('pointermove', onPointerMove);
+}
+
+//
+
+function onPointerMove(e) {
+    pointer.x = e.clientX;
+    pointer.y = e.clientY;
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function pick() {
+    // render the picking scene off-screen
+    // set the view offset to represent just a single pixel under the mouse
+    const dpr = window.devicePixelRatio;
+    camera.setViewOffset(
+        renderer.domElement.width,
+        renderer.domElement.height,
+        Math.floor(pointer.x * dpr),
+        Math.floor(pointer.y * dpr),
+        1,
+        1,
+    );
+
+    // render the scene
+    renderer.setRenderTarget(pickingTexture);
+
+    // clear the background to - 1 meaning no item was hit
+    clearColor.setRGB(-1, -1, -1);
+    renderer.setClearColor(clearColor);
+    renderer.render(pickingScene, camera);
+
+    // clear the view offset so rendering returns to normal
+    camera.clearViewOffset();
+
+    // create buffer for reading single pixel
+    const pixelBuffer = new Int32Array(4);
+
+    // read the pixel
+    renderer.readRenderTargetPixelsAsync(pickingTexture, 0, 0, 1, 1, pixelBuffer).then(() => {
+        const id = pixelBuffer[0];
+        if (id !== -1) {
+            // move our highlightBox so that it surrounds the picked object
+            const data = pickingData[id];
+            highlightBox.position.copy(data.position);
+            highlightBox.rotation.copy(data.rotation);
+            highlightBox.scale.copy(data.scale).add(offset);
+            highlightBox.visible = true;
+        } else {
+            highlightBox.visible = false;
+        }
+    });
+}
+
+function render() {
+    controls.update();
+
+    pick();
+
+    renderer.setRenderTarget(null);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_cubes_ortho.ts b/examples-testing/examples/webgl_interactive_cubes_ortho.ts
new file mode 100644
index 000000000..520674b5f
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_cubes_ortho.ts
@@ -0,0 +1,129 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let stats;
+let camera, scene, raycaster, renderer;
+
+let theta = 0;
+let INTERSECTED;
+
+const pointer = new THREE.Vector2();
+const radius = 25;
+const frustumSize = 50;
+
+init();
+
+function init() {
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera(
+        (frustumSize * aspect) / -2,
+        (frustumSize * aspect) / 2,
+        frustumSize / 2,
+        frustumSize / -2,
+        0.1,
+        100,
+    );
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1).normalize();
+    scene.add(light);
+
+    const geometry = new THREE.BoxGeometry();
+
+    for (let i = 0; i < 2000; i++) {
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
+
+        object.position.x = Math.random() * 40 - 20;
+        object.position.y = Math.random() * 40 - 20;
+        object.position.z = Math.random() * 40 - 20;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        object.scale.z = Math.random() + 0.5;
+
+        scene.add(object);
+    }
+
+    raycaster = new THREE.Raycaster();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    document.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera.left = (-frustumSize * aspect) / 2;
+    camera.right = (frustumSize * aspect) / 2;
+    camera.top = frustumSize / 2;
+    camera.bottom = -frustumSize / 2;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    theta += 0.1;
+
+    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
+    camera.lookAt(scene.position);
+
+    camera.updateMatrixWorld();
+
+    // find intersections
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObjects(scene.children, false);
+
+    if (intersects.length > 0) {
+        if (INTERSECTED != intersects[0].object) {
+            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
+
+            INTERSECTED = intersects[0].object;
+            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
+            INTERSECTED.material.emissive.setHex(0xff0000);
+        }
+    } else {
+        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
+
+        INTERSECTED = null;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_lines.ts b/examples-testing/examples/webgl_interactive_lines.ts
new file mode 100644
index 000000000..b137c5501
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_lines.ts
@@ -0,0 +1,160 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+let camera, scene, raycaster, renderer, parentTransform, sphereInter;
+
+const pointer = new THREE.Vector2();
+const radius = 100;
+let theta = 0;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    const info = document.createElement('div');
+    info.style.position = 'absolute';
+    info.style.top = '10px';
+    info.style.width = '100%';
+    info.style.textAlign = 'center';
+    info.innerHTML =
+        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - interactive lines';
+    container.appendChild(info);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const geometry = new THREE.SphereGeometry(5);
+    const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
+
+    sphereInter = new THREE.Mesh(geometry, material);
+    sphereInter.visible = false;
+    scene.add(sphereInter);
+
+    const lineGeometry = new THREE.BufferGeometry();
+    const points = [];
+
+    const point = new THREE.Vector3();
+    const direction = new THREE.Vector3();
+
+    for (let i = 0; i < 50; i++) {
+        direction.x += Math.random() - 0.5;
+        direction.y += Math.random() - 0.5;
+        direction.z += Math.random() - 0.5;
+        direction.normalize().multiplyScalar(10);
+
+        point.add(direction);
+        points.push(point.x, point.y, point.z);
+    }
+
+    lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
+
+    parentTransform = new THREE.Object3D();
+    parentTransform.position.x = Math.random() * 40 - 20;
+    parentTransform.position.y = Math.random() * 40 - 20;
+    parentTransform.position.z = Math.random() * 40 - 20;
+
+    parentTransform.rotation.x = Math.random() * 2 * Math.PI;
+    parentTransform.rotation.y = Math.random() * 2 * Math.PI;
+    parentTransform.rotation.z = Math.random() * 2 * Math.PI;
+
+    parentTransform.scale.x = Math.random() + 0.5;
+    parentTransform.scale.y = Math.random() + 0.5;
+    parentTransform.scale.z = Math.random() + 0.5;
+
+    for (let i = 0; i < 50; i++) {
+        let object;
+
+        const lineMaterial = new THREE.LineBasicMaterial({ color: Math.random() * 0xffffff });
+
+        if (Math.random() > 0.5) {
+            object = new THREE.Line(lineGeometry, lineMaterial);
+        } else {
+            object = new THREE.LineSegments(lineGeometry, lineMaterial);
+        }
+
+        object.position.x = Math.random() * 400 - 200;
+        object.position.y = Math.random() * 400 - 200;
+        object.position.z = Math.random() * 400 - 200;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        object.scale.z = Math.random() + 0.5;
+
+        parentTransform.add(object);
+    }
+
+    scene.add(parentTransform);
+
+    raycaster = new THREE.Raycaster();
+    raycaster.params.Line.threshold = 3;
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    theta += 0.1;
+
+    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
+    camera.lookAt(scene.position);
+
+    camera.updateMatrixWorld();
+
+    // find intersections
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObjects(parentTransform.children, true);
+
+    if (intersects.length > 0) {
+        sphereInter.visible = true;
+        sphereInter.position.copy(intersects[0].point);
+    } else {
+        sphereInter.visible = false;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_points.ts b/examples-testing/examples/webgl_interactive_points.ts
new file mode 100644
index 000000000..93113b867
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_points.ts
@@ -0,0 +1,143 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+let renderer, scene, camera, stats;
+
+let particles;
+
+const PARTICLE_SIZE = 20;
+
+let raycaster, intersects;
+let pointer, INTERSECTED;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 250;
+
+    //
+
+    let boxGeometry = new THREE.BoxGeometry(200, 200, 200, 16, 16, 16);
+
+    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
+
+    boxGeometry.deleteAttribute('normal');
+    boxGeometry.deleteAttribute('uv');
+
+    boxGeometry = BufferGeometryUtils.mergeVertices(boxGeometry);
+
+    //
+
+    const positionAttribute = boxGeometry.getAttribute('position');
+
+    const colors = [];
+    const sizes = [];
+
+    const color = new THREE.Color();
+
+    for (let i = 0, l = positionAttribute.count; i < l; i++) {
+        color.setHSL(0.01 + 0.1 * (i / l), 1.0, 0.5);
+        color.toArray(colors, i * 3);
+
+        sizes[i] = PARTICLE_SIZE * 0.5;
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', positionAttribute);
+    geometry.setAttribute('customColor', new THREE.Float32BufferAttribute(colors, 3));
+    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
+
+    //
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            color: { value: new THREE.Color(0xffffff) },
+            pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/disc.png') },
+            alphaTest: { value: 0.9 },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    //
+
+    particles = new THREE.Points(geometry, material);
+    scene.add(particles);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    raycaster = new THREE.Raycaster();
+    pointer = new THREE.Vector2();
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('pointermove', onPointerMove);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    particles.rotation.x += 0.0005;
+    particles.rotation.y += 0.001;
+
+    const geometry = particles.geometry;
+    const attributes = geometry.attributes;
+
+    raycaster.setFromCamera(pointer, camera);
+
+    intersects = raycaster.intersectObject(particles);
+
+    if (intersects.length > 0) {
+        if (INTERSECTED != intersects[0].index) {
+            attributes.size.array[INTERSECTED] = PARTICLE_SIZE;
+
+            INTERSECTED = intersects[0].index;
+
+            attributes.size.array[INTERSECTED] = PARTICLE_SIZE * 1.25;
+            attributes.size.needsUpdate = true;
+        }
+    } else if (INTERSECTED !== null) {
+        attributes.size.array[INTERSECTED] = PARTICLE_SIZE;
+        attributes.size.needsUpdate = true;
+        INTERSECTED = null;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_raycasting_points.ts b/examples-testing/examples/webgl_interactive_raycasting_points.ts
new file mode 100644
index 000000000..41c158a43
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_raycasting_points.ts
@@ -0,0 +1,220 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let renderer, scene, camera, stats;
+let pointclouds;
+let raycaster;
+let intersection = null;
+let spheresIndex = 0;
+let clock;
+let toggle = 0;
+
+const pointer = new THREE.Vector2();
+const spheres = [];
+
+const threshold = 0.1;
+const pointSize = 0.05;
+const width = 80;
+const length = 160;
+const rotateY = new THREE.Matrix4().makeRotationY(0.005);
+
+init();
+
+function generatePointCloudGeometry(color, width, length) {
+    const geometry = new THREE.BufferGeometry();
+    const numPoints = width * length;
+
+    const positions = new Float32Array(numPoints * 3);
+    const colors = new Float32Array(numPoints * 3);
+
+    let k = 0;
+
+    for (let i = 0; i < width; i++) {
+        for (let j = 0; j < length; j++) {
+            const u = i / width;
+            const v = j / length;
+            const x = u - 0.5;
+            const y = (Math.cos(u * Math.PI * 4) + Math.sin(v * Math.PI * 8)) / 20;
+            const z = v - 0.5;
+
+            positions[3 * k] = x;
+            positions[3 * k + 1] = y;
+            positions[3 * k + 2] = z;
+
+            const intensity = (y + 0.1) * 5;
+            colors[3 * k] = color.r * intensity;
+            colors[3 * k + 1] = color.g * intensity;
+            colors[3 * k + 2] = color.b * intensity;
+
+            k++;
+        }
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
+    geometry.computeBoundingBox();
+
+    return geometry;
+}
+
+function generatePointcloud(color, width, length) {
+    const geometry = generatePointCloudGeometry(color, width, length);
+    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
+
+    return new THREE.Points(geometry, material);
+}
+
+function generateIndexedPointcloud(color, width, length) {
+    const geometry = generatePointCloudGeometry(color, width, length);
+    const numPoints = width * length;
+    const indices = new Uint16Array(numPoints);
+
+    let k = 0;
+
+    for (let i = 0; i < width; i++) {
+        for (let j = 0; j < length; j++) {
+            indices[k] = k;
+            k++;
+        }
+    }
+
+    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
+
+    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
+
+    return new THREE.Points(geometry, material);
+}
+
+function generateIndexedWithOffsetPointcloud(color, width, length) {
+    const geometry = generatePointCloudGeometry(color, width, length);
+    const numPoints = width * length;
+    const indices = new Uint16Array(numPoints);
+
+    let k = 0;
+
+    for (let i = 0; i < width; i++) {
+        for (let j = 0; j < length; j++) {
+            indices[k] = k;
+            k++;
+        }
+    }
+
+    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
+    geometry.addGroup(0, indices.length);
+
+    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
+
+    return new THREE.Points(geometry, material);
+}
+
+function init() {
+    const container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(10, 10, 10);
+    camera.lookAt(scene.position);
+    camera.updateMatrix();
+
+    //
+
+    const pcBuffer = generatePointcloud(new THREE.Color(1, 0, 0), width, length);
+    pcBuffer.scale.set(5, 10, 10);
+    pcBuffer.position.set(-5, 0, 0);
+    scene.add(pcBuffer);
+
+    const pcIndexed = generateIndexedPointcloud(new THREE.Color(0, 1, 0), width, length);
+    pcIndexed.scale.set(5, 10, 10);
+    pcIndexed.position.set(0, 0, 0);
+    scene.add(pcIndexed);
+
+    const pcIndexedOffset = generateIndexedWithOffsetPointcloud(new THREE.Color(0, 1, 1), width, length);
+    pcIndexedOffset.scale.set(5, 10, 10);
+    pcIndexedOffset.position.set(5, 0, 0);
+    scene.add(pcIndexedOffset);
+
+    pointclouds = [pcBuffer, pcIndexed, pcIndexedOffset];
+
+    //
+
+    const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
+    const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
+
+    for (let i = 0; i < 40; i++) {
+        const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
+        scene.add(sphere);
+        spheres.push(sphere);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    raycaster = new THREE.Raycaster();
+    raycaster.params.Points.threshold = threshold;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('pointermove', onPointerMove);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    camera.applyMatrix4(rotateY);
+    camera.updateMatrixWorld();
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersections = raycaster.intersectObjects(pointclouds, false);
+    intersection = intersections.length > 0 ? intersections[0] : null;
+
+    if (toggle > 0.02 && intersection !== null) {
+        spheres[spheresIndex].position.copy(intersection.point);
+        spheres[spheresIndex].scale.set(1, 1, 1);
+        spheresIndex = (spheresIndex + 1) % spheres.length;
+
+        toggle = 0;
+    }
+
+    for (let i = 0; i < spheres.length; i++) {
+        const sphere = spheres[i];
+        sphere.scale.multiplyScalar(0.98);
+        sphere.scale.clampScalar(0.01, 1);
+    }
+
+    toggle += clock.getDelta();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_interactive_voxelpainter.ts b/examples-testing/examples/webgl_interactive_voxelpainter.ts
new file mode 100644
index 000000000..48b16f3b7
--- /dev/null
+++ b/examples-testing/examples/webgl_interactive_voxelpainter.ts
@@ -0,0 +1,158 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let plane;
+let pointer,
+    raycaster,
+    isShiftDown = false;
+
+let rollOverMesh, rollOverMaterial;
+let cubeGeo, cubeMaterial;
+
+const objects = [];
+
+init();
+render();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(500, 800, 1300);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    // roll-over helpers
+
+    const rollOverGeo = new THREE.BoxGeometry(50, 50, 50);
+    rollOverMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true });
+    rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial);
+    scene.add(rollOverMesh);
+
+    // cubes
+
+    const map = new THREE.TextureLoader().load('textures/square-outline-textured.png');
+    map.colorSpace = THREE.SRGBColorSpace;
+    cubeGeo = new THREE.BoxGeometry(50, 50, 50);
+    cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xfeb74c, map: map });
+
+    // grid
+
+    const gridHelper = new THREE.GridHelper(1000, 20);
+    scene.add(gridHelper);
+
+    //
+
+    raycaster = new THREE.Raycaster();
+    pointer = new THREE.Vector2();
+
+    const geometry = new THREE.PlaneGeometry(1000, 1000);
+    geometry.rotateX(-Math.PI / 2);
+
+    plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ visible: false }));
+    scene.add(plane);
+
+    objects.push(plane);
+
+    // lights
+
+    const ambientLight = new THREE.AmbientLight(0x606060, 3);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(1, 0.75, 0.5).normalize();
+    scene.add(directionalLight);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerdown', onPointerDown);
+    document.addEventListener('keydown', onDocumentKeyDown);
+    document.addEventListener('keyup', onDocumentKeyUp);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function onPointerMove(event) {
+    pointer.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObjects(objects, false);
+
+    if (intersects.length > 0) {
+        const intersect = intersects[0];
+
+        rollOverMesh.position.copy(intersect.point).add(intersect.face.normal);
+        rollOverMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
+
+        render();
+    }
+}
+
+function onPointerDown(event) {
+    pointer.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObjects(objects, false);
+
+    if (intersects.length > 0) {
+        const intersect = intersects[0];
+
+        // delete cube
+
+        if (isShiftDown) {
+            if (intersect.object !== plane) {
+                scene.remove(intersect.object);
+
+                objects.splice(objects.indexOf(intersect.object), 1);
+            }
+
+            // create cube
+        } else {
+            const voxel = new THREE.Mesh(cubeGeo, cubeMaterial);
+            voxel.position.copy(intersect.point).add(intersect.face.normal);
+            voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
+            scene.add(voxel);
+
+            objects.push(voxel);
+        }
+
+        render();
+    }
+}
+
+function onDocumentKeyDown(event) {
+    switch (event.keyCode) {
+        case 16:
+            isShiftDown = true;
+            break;
+    }
+}
+
+function onDocumentKeyUp(event) {
+    switch (event.keyCode) {
+        case 16:
+            isShiftDown = false;
+            break;
+    }
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_layers.ts b/examples-testing/examples/webgl_layers.ts
new file mode 100644
index 000000000..8bdcda7f9
--- /dev/null
+++ b/examples-testing/examples/webgl_layers.ts
@@ -0,0 +1,125 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let container, stats;
+let camera, scene, renderer;
+
+let theta = 0;
+const radius = 5;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.layers.enable(0); // enabled by default
+    camera.layers.enable(1);
+    camera.layers.enable(2);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
+    light.layers.enable(0);
+    light.layers.enable(1);
+    light.layers.enable(2);
+
+    scene.add(camera);
+    camera.add(light);
+
+    const colors = [0xff0000, 0x00ff00, 0x0000ff];
+    const geometry = new THREE.BoxGeometry();
+
+    for (let i = 0; i < 300; i++) {
+        const layer = i % 3;
+
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: colors[layer] }));
+
+        object.position.x = Math.random() * 40 - 20;
+        object.position.y = Math.random() * 40 - 20;
+        object.position.z = Math.random() * 40 - 20;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        object.scale.z = Math.random() + 0.5;
+
+        object.layers.set(layer);
+
+        scene.add(object);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const layers = {
+        'toggle red': function () {
+            camera.layers.toggle(0);
+        },
+
+        'toggle green': function () {
+            camera.layers.toggle(1);
+        },
+
+        'toggle blue': function () {
+            camera.layers.toggle(2);
+        },
+
+        'enable all': function () {
+            camera.layers.enableAll();
+        },
+
+        'disable all': function () {
+            camera.layers.disableAll();
+        },
+    };
+
+    //
+    // Init gui
+    const gui = new GUI();
+    gui.add(layers, 'toggle red');
+    gui.add(layers, 'toggle green');
+    gui.add(layers, 'toggle blue');
+    gui.add(layers, 'enable all');
+    gui.add(layers, 'disable all');
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    theta += 0.1;
+
+    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
+    camera.lookAt(scene.position);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lensflares.ts b/examples-testing/examples/webgl_lensflares.ts
new file mode 100644
index 000000000..230cebfa0
--- /dev/null
+++ b/examples-testing/examples/webgl_lensflares.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FlyControls } from 'three/addons/controls/FlyControls.js';
+import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+let controls;
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 15000);
+    camera.position.z = 250;
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color().setHSL(0.51, 0.4, 0.01, THREE.SRGBColorSpace);
+    scene.fog = new THREE.Fog(scene.background, 3500, 15000);
+
+    // world
+
+    const s = 250;
+
+    const geometry = new THREE.BoxGeometry(s, s, s);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0xffffff, shininess: 50 });
+
+    for (let i = 0; i < 3000; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = 8000 * (2.0 * Math.random() - 1.0);
+        mesh.position.y = 8000 * (2.0 * Math.random() - 1.0);
+        mesh.position.z = 8000 * (2.0 * Math.random() - 1.0);
+
+        mesh.rotation.x = Math.random() * Math.PI;
+        mesh.rotation.y = Math.random() * Math.PI;
+        mesh.rotation.z = Math.random() * Math.PI;
+
+        mesh.matrixAutoUpdate = false;
+        mesh.updateMatrix();
+
+        scene.add(mesh);
+    }
+
+    // lights
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 0.15);
+    dirLight.position.set(0, -1, 0).normalize();
+    dirLight.color.setHSL(0.1, 0.7, 0.5);
+    scene.add(dirLight);
+
+    // lensflares
+    const textureLoader = new THREE.TextureLoader();
+
+    const textureFlare0 = textureLoader.load('textures/lensflare/lensflare0.png');
+    const textureFlare3 = textureLoader.load('textures/lensflare/lensflare3.png');
+
+    addLight(0.55, 0.9, 0.5, 5000, 0, -1000);
+    addLight(0.08, 0.8, 0.5, 0, 0, -1000);
+    addLight(0.995, 0.5, 0.9, 5000, 5000, -1000);
+
+    function addLight(h, s, l, x, y, z) {
+        const light = new THREE.PointLight(0xffffff, 1.5, 2000, 0);
+        light.color.setHSL(h, s, l);
+        light.position.set(x, y, z);
+        scene.add(light);
+
+        const lensflare = new Lensflare();
+        lensflare.addElement(new LensflareElement(textureFlare0, 700, 0, light.color));
+        lensflare.addElement(new LensflareElement(textureFlare3, 60, 0.6));
+        lensflare.addElement(new LensflareElement(textureFlare3, 70, 0.7));
+        lensflare.addElement(new LensflareElement(textureFlare3, 120, 0.9));
+        lensflare.addElement(new LensflareElement(textureFlare3, 70, 1));
+        light.add(lensflare);
+    }
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    controls = new FlyControls(camera, renderer.domElement);
+
+    controls.movementSpeed = 2500;
+    controls.domElement = container;
+    controls.rollSpeed = Math.PI / 6;
+    controls.autoForward = false;
+    controls.dragToLook = false;
+
+    // stats
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // events
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    controls.update(delta);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lightprobe.ts b/examples-testing/examples/webgl_lightprobe.ts
new file mode 100644
index 000000000..2efcad52a
--- /dev/null
+++ b/examples-testing/examples/webgl_lightprobe.ts
@@ -0,0 +1,133 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
+
+let mesh, renderer, scene, camera;
+
+let gui;
+
+let lightProbe;
+let directionalLight;
+
+// linear color space
+const API = {
+    lightProbeIntensity: 1.0,
+    directionalLightIntensity: 0.6,
+    envMapIntensity: 1,
+};
+
+init();
+
+function init() {
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    // tone mapping
+    renderer.toneMapping = THREE.NoToneMapping;
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 30);
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 10;
+    controls.maxDistance = 50;
+    controls.enablePan = false;
+
+    // probe
+    lightProbe = new THREE.LightProbe();
+    scene.add(lightProbe);
+
+    // light
+    directionalLight = new THREE.DirectionalLight(0xffffff, API.directionalLightIntensity);
+    directionalLight.position.set(10, 10, 10);
+    scene.add(directionalLight);
+
+    // envmap
+    const genCubeUrls = function (prefix, postfix) {
+        return [
+            prefix + 'px' + postfix,
+            prefix + 'nx' + postfix,
+            prefix + 'py' + postfix,
+            prefix + 'ny' + postfix,
+            prefix + 'pz' + postfix,
+            prefix + 'nz' + postfix,
+        ];
+    };
+
+    const urls = genCubeUrls('textures/cube/pisa/', '.png');
+
+    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
+        scene.background = cubeTexture;
+
+        lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture));
+
+        const geometry = new THREE.SphereGeometry(5, 64, 32);
+        //const geometry = new THREE.TorusKnotGeometry( 4, 1.5, 256, 32, 2, 3 );
+
+        const material = new THREE.MeshStandardMaterial({
+            color: 0xffffff,
+            metalness: 0,
+            roughness: 0,
+            envMap: cubeTexture,
+            envMapIntensity: API.envMapIntensity,
+        });
+
+        // mesh
+        mesh = new THREE.Mesh(geometry, material);
+        scene.add(mesh);
+
+        render();
+    });
+
+    // gui
+    gui = new GUI({ title: 'Intensity' });
+
+    gui.add(API, 'lightProbeIntensity', 0, 1, 0.02)
+        .name('light probe')
+        .onChange(function () {
+            lightProbe.intensity = API.lightProbeIntensity;
+            render();
+        });
+
+    gui.add(API, 'directionalLightIntensity', 0, 1, 0.02)
+        .name('directional light')
+        .onChange(function () {
+            directionalLight.intensity = API.directionalLightIntensity;
+            render();
+        });
+
+    gui.add(API, 'envMapIntensity', 0, 1, 0.02)
+        .name('envMap')
+        .onChange(function () {
+            mesh.material.envMapIntensity = API.envMapIntensity;
+            render();
+        });
+
+    // listener
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lightprobe_cubecamera.ts b/examples-testing/examples/webgl_lightprobe_cubecamera.ts
new file mode 100644
index 000000000..c714d2978
--- /dev/null
+++ b/examples-testing/examples/webgl_lightprobe_cubecamera.ts
@@ -0,0 +1,83 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LightProbeHelper } from 'three/addons/helpers/LightProbeHelper.js';
+import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
+
+let renderer, scene, camera, cubeCamera;
+
+let lightProbe;
+
+init();
+
+function init() {
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 30);
+
+    const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
+
+    cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget);
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 10;
+    controls.maxDistance = 50;
+    controls.enablePan = false;
+
+    // probe
+    lightProbe = new THREE.LightProbe();
+    scene.add(lightProbe);
+
+    // envmap
+    const genCubeUrls = function (prefix, postfix) {
+        return [
+            prefix + 'px' + postfix,
+            prefix + 'nx' + postfix,
+            prefix + 'py' + postfix,
+            prefix + 'ny' + postfix,
+            prefix + 'pz' + postfix,
+            prefix + 'nz' + postfix,
+        ];
+    };
+
+    const urls = genCubeUrls('textures/cube/pisa/', '.png');
+
+    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
+        scene.background = cubeTexture;
+
+        cubeCamera.update(renderer, scene);
+
+        lightProbe.copy(LightProbeGenerator.fromCubeRenderTarget(renderer, cubeRenderTarget));
+
+        scene.add(new LightProbeHelper(lightProbe, 5));
+
+        render();
+    });
+
+    // listener
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lights_hemisphere.ts b/examples-testing/examples/webgl_lights_hemisphere.ts
new file mode 100644
index 000000000..15bc76099
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_hemisphere.ts
@@ -0,0 +1,188 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer;
+const mixers = [];
+let stats;
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.set(0, 0, 250);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color().setHSL(0.6, 0, 1);
+    scene.fog = new THREE.Fog(scene.background, 1, 5000);
+
+    // LIGHTS
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 2);
+    hemiLight.color.setHSL(0.6, 1, 0.6);
+    hemiLight.groundColor.setHSL(0.095, 1, 0.75);
+    hemiLight.position.set(0, 50, 0);
+    scene.add(hemiLight);
+
+    const hemiLightHelper = new THREE.HemisphereLightHelper(hemiLight, 10);
+    scene.add(hemiLightHelper);
+
+    //
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.color.setHSL(0.1, 1, 0.95);
+    dirLight.position.set(-1, 1.75, 1);
+    dirLight.position.multiplyScalar(30);
+    scene.add(dirLight);
+
+    dirLight.castShadow = true;
+
+    dirLight.shadow.mapSize.width = 2048;
+    dirLight.shadow.mapSize.height = 2048;
+
+    const d = 50;
+
+    dirLight.shadow.camera.left = -d;
+    dirLight.shadow.camera.right = d;
+    dirLight.shadow.camera.top = d;
+    dirLight.shadow.camera.bottom = -d;
+
+    dirLight.shadow.camera.far = 3500;
+    dirLight.shadow.bias = -0.0001;
+
+    const dirLightHelper = new THREE.DirectionalLightHelper(dirLight, 10);
+    scene.add(dirLightHelper);
+
+    // GROUND
+
+    const groundGeo = new THREE.PlaneGeometry(10000, 10000);
+    const groundMat = new THREE.MeshLambertMaterial({ color: 0xffffff });
+    groundMat.color.setHSL(0.095, 1, 0.75);
+
+    const ground = new THREE.Mesh(groundGeo, groundMat);
+    ground.position.y = -33;
+    ground.rotation.x = -Math.PI / 2;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // SKYDOME
+
+    const vertexShader = document.getElementById('vertexShader').textContent;
+    const fragmentShader = document.getElementById('fragmentShader').textContent;
+    const uniforms = {
+        topColor: { value: new THREE.Color(0x0077ff) },
+        bottomColor: { value: new THREE.Color(0xffffff) },
+        offset: { value: 33 },
+        exponent: { value: 0.6 },
+    };
+    uniforms['topColor'].value.copy(hemiLight.color);
+
+    scene.fog.color.copy(uniforms['bottomColor'].value);
+
+    const skyGeo = new THREE.SphereGeometry(4000, 32, 15);
+    const skyMat = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: vertexShader,
+        fragmentShader: fragmentShader,
+        side: THREE.BackSide,
+    });
+
+    const sky = new THREE.Mesh(skyGeo, skyMat);
+    scene.add(sky);
+
+    // MODEL
+
+    const loader = new GLTFLoader();
+
+    loader.load('models/gltf/Flamingo.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+
+        const s = 0.35;
+        mesh.scale.set(s, s, s);
+        mesh.position.y = 15;
+        mesh.rotation.y = -1;
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+
+        const mixer = new THREE.AnimationMixer(mesh);
+        mixer.clipAction(gltf.animations[0]).setDuration(1).play();
+        mixers.push(mixer);
+    });
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+    renderer.shadowMap.enabled = true;
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    const params = {
+        toggleHemisphereLight: function () {
+            hemiLight.visible = !hemiLight.visible;
+            hemiLightHelper.visible = !hemiLightHelper.visible;
+        },
+        toggleDirectionalLight: function () {
+            dirLight.visible = !dirLight.visible;
+            dirLightHelper.visible = !dirLightHelper.visible;
+        },
+        shadowIntensity: 1,
+    };
+
+    const gui = new GUI();
+
+    gui.add(params, 'toggleHemisphereLight').name('toggle hemisphere light');
+    gui.add(params, 'toggleDirectionalLight').name('toggle directional light');
+    gui.add(params, 'shadowIntensity', 0, 1)
+        .name('shadow intensity')
+        .onChange(value => {
+            dirLight.shadow.intensity = value;
+        });
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    for (let i = 0; i < mixers.length; i++) {
+        mixers[i].update(delta);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lights_physical.ts b/examples-testing/examples/webgl_lights_physical.ts
new file mode 100644
index 000000000..707ef200e
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_physical.ts
@@ -0,0 +1,237 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, bulbLight, bulbMat, hemiLight, stats;
+let ballMat, cubeMat, floorMat;
+
+let previousShadowMap = false;
+
+// ref for lumens: http://www.power-sure.com/lumens.htm
+const bulbLuminousPowers = {
+    '110000 lm (1000W)': 110000,
+    '3500 lm (300W)': 3500,
+    '1700 lm (100W)': 1700,
+    '800 lm (60W)': 800,
+    '400 lm (40W)': 400,
+    '180 lm (25W)': 180,
+    '20 lm (4W)': 20,
+    Off: 0,
+};
+
+// ref for solar irradiances: https://en.wikipedia.org/wiki/Lux
+const hemiLuminousIrradiances = {
+    '0.0001 lx (Moonless Night)': 0.0001,
+    '0.002 lx (Night Airglow)': 0.002,
+    '0.5 lx (Full Moon)': 0.5,
+    '3.4 lx (City Twilight)': 3.4,
+    '50 lx (Living Room)': 50,
+    '100 lx (Very Overcast)': 100,
+    '350 lx (Office Room)': 350,
+    '400 lx (Sunrise/Sunset)': 400,
+    '1000 lx (Overcast)': 1000,
+    '18000 lx (Daylight)': 18000,
+    '50000 lx (Direct Sun)': 50000,
+};
+
+const params = {
+    shadows: true,
+    exposure: 0.68,
+    bulbPower: Object.keys(bulbLuminousPowers)[4],
+    hemiIrradiance: Object.keys(hemiLuminousIrradiances)[0],
+};
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.x = -4;
+    camera.position.z = 4;
+    camera.position.y = 2;
+
+    scene = new THREE.Scene();
+
+    const bulbGeometry = new THREE.SphereGeometry(0.02, 16, 8);
+    bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);
+
+    bulbMat = new THREE.MeshStandardMaterial({
+        emissive: 0xffffee,
+        emissiveIntensity: 1,
+        color: 0x000000,
+    });
+    bulbLight.add(new THREE.Mesh(bulbGeometry, bulbMat));
+    bulbLight.position.set(0, 2, 0);
+    bulbLight.castShadow = true;
+    scene.add(bulbLight);
+
+    hemiLight = new THREE.HemisphereLight(0xddeeff, 0x0f0e0d, 0.02);
+    scene.add(hemiLight);
+
+    floorMat = new THREE.MeshStandardMaterial({
+        roughness: 0.8,
+        color: 0xffffff,
+        metalness: 0.2,
+        bumpScale: 1,
+    });
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 4;
+        map.repeat.set(10, 24);
+        map.colorSpace = THREE.SRGBColorSpace;
+        floorMat.map = map;
+        floorMat.needsUpdate = true;
+    });
+    textureLoader.load('textures/hardwood2_bump.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 4;
+        map.repeat.set(10, 24);
+        floorMat.bumpMap = map;
+        floorMat.needsUpdate = true;
+    });
+    textureLoader.load('textures/hardwood2_roughness.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 4;
+        map.repeat.set(10, 24);
+        floorMat.roughnessMap = map;
+        floorMat.needsUpdate = true;
+    });
+
+    cubeMat = new THREE.MeshStandardMaterial({
+        roughness: 0.7,
+        color: 0xffffff,
+        bumpScale: 1,
+        metalness: 0.2,
+    });
+    textureLoader.load('textures/brick_diffuse.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 4;
+        map.repeat.set(1, 1);
+        map.colorSpace = THREE.SRGBColorSpace;
+        cubeMat.map = map;
+        cubeMat.needsUpdate = true;
+    });
+    textureLoader.load('textures/brick_bump.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 4;
+        map.repeat.set(1, 1);
+        cubeMat.bumpMap = map;
+        cubeMat.needsUpdate = true;
+    });
+
+    ballMat = new THREE.MeshStandardMaterial({
+        color: 0xffffff,
+        roughness: 0.5,
+        metalness: 1.0,
+    });
+    textureLoader.load('textures/planets/earth_atmos_2048.jpg', function (map) {
+        map.anisotropy = 4;
+        map.colorSpace = THREE.SRGBColorSpace;
+        ballMat.map = map;
+        ballMat.needsUpdate = true;
+    });
+    textureLoader.load('textures/planets/earth_specular_2048.jpg', function (map) {
+        map.anisotropy = 4;
+        map.colorSpace = THREE.SRGBColorSpace;
+        ballMat.metalnessMap = map;
+        ballMat.needsUpdate = true;
+    });
+
+    const floorGeometry = new THREE.PlaneGeometry(20, 20);
+    const floorMesh = new THREE.Mesh(floorGeometry, floorMat);
+    floorMesh.receiveShadow = true;
+    floorMesh.rotation.x = -Math.PI / 2.0;
+    scene.add(floorMesh);
+
+    const ballGeometry = new THREE.SphereGeometry(0.25, 32, 32);
+    const ballMesh = new THREE.Mesh(ballGeometry, ballMat);
+    ballMesh.position.set(1, 0.25, 1);
+    ballMesh.rotation.y = Math.PI;
+    ballMesh.castShadow = true;
+    scene.add(ballMesh);
+
+    const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
+    const boxMesh = new THREE.Mesh(boxGeometry, cubeMat);
+    boxMesh.position.set(-0.5, 0.25, -1);
+    boxMesh.castShadow = true;
+    scene.add(boxMesh);
+
+    const boxMesh2 = new THREE.Mesh(boxGeometry, cubeMat);
+    boxMesh2.position.set(0, 0.25, -5);
+    boxMesh2.castShadow = true;
+    scene.add(boxMesh2);
+
+    const boxMesh3 = new THREE.Mesh(boxGeometry, cubeMat);
+    boxMesh3.position.set(7, 0.25, 0);
+    boxMesh3.castShadow = true;
+    scene.add(boxMesh3);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 20;
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'hemiIrradiance', Object.keys(hemiLuminousIrradiances));
+    gui.add(params, 'bulbPower', Object.keys(bulbLuminousPowers));
+    gui.add(params, 'exposure', 0, 1);
+    gui.add(params, 'shadows');
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.toneMappingExposure = Math.pow(params.exposure, 5.0); // to allow for very bright scenes.
+    renderer.shadowMap.enabled = params.shadows;
+    bulbLight.castShadow = params.shadows;
+
+    if (params.shadows !== previousShadowMap) {
+        ballMat.needsUpdate = true;
+        cubeMat.needsUpdate = true;
+        floorMat.needsUpdate = true;
+        previousShadowMap = params.shadows;
+    }
+
+    bulbLight.power = bulbLuminousPowers[params.bulbPower];
+    bulbMat.emissiveIntensity = bulbLight.intensity / Math.pow(0.02, 2.0); // convert from intensity to irradiance at bulb surface
+
+    hemiLight.intensity = hemiLuminousIrradiances[params.hemiIrradiance];
+    const time = Date.now() * 0.0005;
+
+    bulbLight.position.y = Math.cos(time) * 0.75 + 1.25;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_lights_pointlights.ts b/examples-testing/examples/webgl_lights_pointlights.ts
new file mode 100644
index 000000000..ea95070ce
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_pointlights.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+
+let camera, scene, renderer, light1, light2, light3, light4, object, stats;
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 100;
+
+    scene = new THREE.Scene();
+
+    //model
+
+    const loader = new OBJLoader();
+    loader.load('models/obj/walt/WaltHead.obj', function (obj) {
+        object = obj;
+        object.scale.multiplyScalar(0.8);
+        object.position.y = -30;
+        scene.add(object);
+    });
+
+    const sphere = new THREE.SphereGeometry(0.5, 16, 8);
+
+    //lights
+
+    light1 = new THREE.PointLight(0xff0040, 400);
+    light1.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xff0040 })));
+    scene.add(light1);
+
+    light2 = new THREE.PointLight(0x0040ff, 400);
+    light2.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0x0040ff })));
+    scene.add(light2);
+
+    light3 = new THREE.PointLight(0x80ff80, 400);
+    light3.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0x80ff80 })));
+    scene.add(light3);
+
+    light4 = new THREE.PointLight(0xffaa00, 400);
+    light4.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xffaa00 })));
+    scene.add(light4);
+
+    //renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.0005;
+    const delta = clock.getDelta();
+
+    if (object) object.rotation.y -= 0.5 * delta;
+
+    light1.position.x = Math.sin(time * 0.7) * 30;
+    light1.position.y = Math.cos(time * 0.5) * 40;
+    light1.position.z = Math.cos(time * 0.3) * 30;
+
+    light2.position.x = Math.cos(time * 0.3) * 30;
+    light2.position.y = Math.sin(time * 0.5) * 40;
+    light2.position.z = Math.sin(time * 0.7) * 30;
+
+    light3.position.x = Math.sin(time * 0.7) * 30;
+    light3.position.y = Math.cos(time * 0.3) * 40;
+    light3.position.z = Math.sin(time * 0.5) * 30;
+
+    light4.position.x = Math.sin(time * 0.3) * 30;
+    light4.position.y = Math.cos(time * 0.7) * 40;
+    light4.position.z = Math.sin(time * 0.5) * 30;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lights_rectarealight.ts b/examples-testing/examples/webgl_lights_rectarealight.ts
new file mode 100644
index 000000000..b841fa6b5
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_rectarealight.ts
@@ -0,0 +1,79 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
+import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
+
+let renderer, scene, camera;
+let stats, meshKnot;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animation);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 5, -15);
+
+    scene = new THREE.Scene();
+
+    RectAreaLightUniformsLib.init();
+
+    const rectLight1 = new THREE.RectAreaLight(0xff0000, 5, 4, 10);
+    rectLight1.position.set(-5, 5, 5);
+    scene.add(rectLight1);
+
+    const rectLight2 = new THREE.RectAreaLight(0x00ff00, 5, 4, 10);
+    rectLight2.position.set(0, 5, 5);
+    scene.add(rectLight2);
+
+    const rectLight3 = new THREE.RectAreaLight(0x0000ff, 5, 4, 10);
+    rectLight3.position.set(5, 5, 5);
+    scene.add(rectLight3);
+
+    scene.add(new RectAreaLightHelper(rectLight1));
+    scene.add(new RectAreaLightHelper(rectLight2));
+    scene.add(new RectAreaLightHelper(rectLight3));
+
+    const geoFloor = new THREE.BoxGeometry(2000, 0.1, 2000);
+    const matStdFloor = new THREE.MeshStandardMaterial({ color: 0xbcbcbc, roughness: 0.1, metalness: 0 });
+    const mshStdFloor = new THREE.Mesh(geoFloor, matStdFloor);
+    scene.add(mshStdFloor);
+
+    const geoKnot = new THREE.TorusKnotGeometry(1.5, 0.5, 200, 16);
+    const matKnot = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0, metalness: 0 });
+    meshKnot = new THREE.Mesh(geoKnot, matKnot);
+    meshKnot.position.set(0, 5, 0);
+    scene.add(meshKnot);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.copy(meshKnot.position);
+    controls.update();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+function animation(time) {
+    meshKnot.rotation.y = time / 1000;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_lights_spotlight.ts b/examples-testing/examples/webgl_lights_spotlight.ts
new file mode 100644
index 000000000..894abaf6f
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_spotlight.ts
@@ -0,0 +1,183 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let renderer, scene, camera;
+
+let spotLight, lightHelper;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(7, 4, 1);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.maxPolarAngle = Math.PI / 2;
+    controls.target.set(0, 1, 0);
+    controls.update();
+
+    const ambient = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.15);
+    scene.add(ambient);
+
+    const loader = new THREE.TextureLoader().setPath('textures/');
+    const filenames = ['disturb.jpg', 'colors.png', 'uv_grid_opengl.jpg'];
+
+    const textures = { none: null };
+
+    for (let i = 0; i < filenames.length; i++) {
+        const filename = filenames[i];
+
+        const texture = loader.load(filename);
+        texture.minFilter = THREE.LinearFilter;
+        texture.magFilter = THREE.LinearFilter;
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        textures[filename] = texture;
+    }
+
+    spotLight = new THREE.SpotLight(0xffffff, 100);
+    spotLight.position.set(2.5, 5, 2.5);
+    spotLight.angle = Math.PI / 6;
+    spotLight.penumbra = 1;
+    spotLight.decay = 2;
+    spotLight.distance = 0;
+    spotLight.map = textures['disturb.jpg'];
+
+    spotLight.castShadow = true;
+    spotLight.shadow.mapSize.width = 1024;
+    spotLight.shadow.mapSize.height = 1024;
+    spotLight.shadow.camera.near = 1;
+    spotLight.shadow.camera.far = 10;
+    spotLight.shadow.focus = 1;
+    scene.add(spotLight);
+
+    lightHelper = new THREE.SpotLightHelper(spotLight);
+    scene.add(lightHelper);
+
+    //
+
+    const geometry = new THREE.PlaneGeometry(200, 200);
+    const material = new THREE.MeshLambertMaterial({ color: 0xbcbcbc });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    mesh.position.set(0, -1, 0);
+    mesh.rotation.x = -Math.PI / 2;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    //
+
+    new PLYLoader().load('models/ply/binary/Lucy100k.ply', function (geometry) {
+        geometry.scale(0.0024, 0.0024, 0.0024);
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshLambertMaterial();
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.rotation.y = -Math.PI / 2;
+        mesh.position.y = 0.8;
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+        scene.add(mesh);
+    });
+
+    window.addEventListener('resize', onWindowResize);
+
+    // GUI
+
+    const gui = new GUI();
+
+    const params = {
+        map: textures['disturb.jpg'],
+        color: spotLight.color.getHex(),
+        intensity: spotLight.intensity,
+        distance: spotLight.distance,
+        angle: spotLight.angle,
+        penumbra: spotLight.penumbra,
+        decay: spotLight.decay,
+        focus: spotLight.shadow.focus,
+        shadows: true,
+    };
+
+    gui.add(params, 'map', textures).onChange(function (val) {
+        spotLight.map = val;
+    });
+
+    gui.addColor(params, 'color').onChange(function (val) {
+        spotLight.color.setHex(val);
+    });
+
+    gui.add(params, 'intensity', 0, 500).onChange(function (val) {
+        spotLight.intensity = val;
+    });
+
+    gui.add(params, 'distance', 0, 20).onChange(function (val) {
+        spotLight.distance = val;
+    });
+
+    gui.add(params, 'angle', 0, Math.PI / 3).onChange(function (val) {
+        spotLight.angle = val;
+    });
+
+    gui.add(params, 'penumbra', 0, 1).onChange(function (val) {
+        spotLight.penumbra = val;
+    });
+
+    gui.add(params, 'decay', 1, 2).onChange(function (val) {
+        spotLight.decay = val;
+    });
+
+    gui.add(params, 'focus', 0, 1).onChange(function (val) {
+        spotLight.shadow.focus = val;
+    });
+
+    gui.add(params, 'shadows').onChange(function (val) {
+        renderer.shadowMap.enabled = val;
+
+        scene.traverse(function (child) {
+            if (child.material) {
+                child.material.needsUpdate = true;
+            }
+        });
+    });
+
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = performance.now() / 3000;
+
+    spotLight.position.x = Math.cos(time) * 2.5;
+    spotLight.position.z = Math.sin(time) * 2.5;
+
+    lightHelper.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lights_spotlights.ts b/examples-testing/examples/webgl_lights_spotlights.ts
new file mode 100644
index 000000000..70caf5a58
--- /dev/null
+++ b/examples-testing/examples/webgl_lights_spotlights.ts
@@ -0,0 +1,133 @@
+import * as THREE from 'three';
+import TWEEN from 'three/addons/libs/tween.module.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setAnimationLoop(animate);
+
+const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
+
+const controls = new OrbitControls(camera, renderer.domElement);
+
+const scene = new THREE.Scene();
+
+const matFloor = new THREE.MeshPhongMaterial({ color: 0x808080 });
+const matBox = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
+
+const geoFloor = new THREE.PlaneGeometry(100, 100);
+const geoBox = new THREE.BoxGeometry(0.3, 0.1, 0.2);
+
+const mshFloor = new THREE.Mesh(geoFloor, matFloor);
+mshFloor.rotation.x = -Math.PI * 0.5;
+const mshBox = new THREE.Mesh(geoBox, matBox);
+
+const ambient = new THREE.AmbientLight(0x444444);
+
+const spotLight1 = createSpotlight(0xff7f00);
+const spotLight2 = createSpotlight(0x00ff7f);
+const spotLight3 = createSpotlight(0x7f00ff);
+
+let lightHelper1, lightHelper2, lightHelper3;
+
+function init() {
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+    camera.position.set(4.6, 2.2, -2.1);
+
+    spotLight1.position.set(1.5, 4, 4.5);
+    spotLight2.position.set(0, 4, 3.5);
+    spotLight3.position.set(-1.5, 4, 4.5);
+
+    lightHelper1 = new THREE.SpotLightHelper(spotLight1);
+    lightHelper2 = new THREE.SpotLightHelper(spotLight2);
+    lightHelper3 = new THREE.SpotLightHelper(spotLight3);
+
+    mshFloor.receiveShadow = true;
+    mshFloor.position.set(0, -0.05, 0);
+
+    mshBox.castShadow = true;
+    mshBox.receiveShadow = true;
+    mshBox.position.set(0, 0.5, 0);
+
+    scene.add(mshFloor);
+    scene.add(mshBox);
+    scene.add(ambient);
+    scene.add(spotLight1, spotLight2, spotLight3);
+    scene.add(lightHelper1, lightHelper2, lightHelper3);
+
+    document.body.appendChild(renderer.domElement);
+    window.addEventListener('resize', onWindowResize);
+
+    controls.target.set(0, 0.5, 0);
+    controls.maxPolarAngle = Math.PI / 2;
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+    controls.update();
+}
+
+function createSpotlight(color) {
+    const newObj = new THREE.SpotLight(color, 10);
+
+    newObj.castShadow = true;
+    newObj.angle = 0.3;
+    newObj.penumbra = 0.2;
+    newObj.decay = 2;
+    newObj.distance = 50;
+
+    return newObj;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function tween(light) {
+    new TWEEN.Tween(light)
+        .to(
+            {
+                angle: Math.random() * 0.7 + 0.1,
+                penumbra: Math.random() + 1,
+            },
+            Math.random() * 3000 + 2000,
+        )
+        .easing(TWEEN.Easing.Quadratic.Out)
+        .start();
+
+    new TWEEN.Tween(light.position)
+        .to(
+            {
+                x: Math.random() * 3 - 1.5,
+                y: Math.random() * 1 + 1.5,
+                z: Math.random() * 3 - 1.5,
+            },
+            Math.random() * 3000 + 2000,
+        )
+        .easing(TWEEN.Easing.Quadratic.Out)
+        .start();
+}
+
+function updateTweens() {
+    tween(spotLight1);
+    tween(spotLight2);
+    tween(spotLight3);
+
+    setTimeout(updateTweens, 5000);
+}
+
+function animate() {
+    TWEEN.update();
+
+    if (lightHelper1) lightHelper1.update();
+    if (lightHelper2) lightHelper2.update();
+    if (lightHelper3) lightHelper3.update();
+
+    renderer.render(scene, camera);
+}
+
+init();
+updateTweens();
diff --git a/examples-testing/examples/webgl_lines_colors.ts b/examples-testing/examples/webgl_lines_colors.ts
new file mode 100644
index 000000000..9da19ee2e
--- /dev/null
+++ b/examples-testing/examples/webgl_lines_colors.ts
@@ -0,0 +1,181 @@
+import * as THREE from 'three';
+
+import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(33, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const hilbertPoints = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 200.0, 1, 0, 1, 2, 3, 4, 5, 6, 7);
+
+    const geometry1 = new THREE.BufferGeometry();
+    const geometry2 = new THREE.BufferGeometry();
+    const geometry3 = new THREE.BufferGeometry();
+
+    const subdivisions = 6;
+
+    let vertices = [];
+    let colors1 = [];
+    let colors2 = [];
+    let colors3 = [];
+
+    const point = new THREE.Vector3();
+    const color = new THREE.Color();
+
+    const spline = new THREE.CatmullRomCurve3(hilbertPoints);
+
+    for (let i = 0; i < hilbertPoints.length * subdivisions; i++) {
+        const t = i / (hilbertPoints.length * subdivisions);
+        spline.getPoint(t, point);
+
+        vertices.push(point.x, point.y, point.z);
+
+        color.setHSL(0.6, 1.0, Math.max(0, -point.x / 200) + 0.5, THREE.SRGBColorSpace);
+        colors1.push(color.r, color.g, color.b);
+
+        color.setHSL(0.9, 1.0, Math.max(0, -point.y / 200) + 0.5, THREE.SRGBColorSpace);
+        colors2.push(color.r, color.g, color.b);
+
+        color.setHSL(i / (hilbertPoints.length * subdivisions), 1.0, 0.5, THREE.SRGBColorSpace);
+        colors3.push(color.r, color.g, color.b);
+    }
+
+    geometry1.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry2.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry3.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    geometry1.setAttribute('color', new THREE.Float32BufferAttribute(colors1, 3));
+    geometry2.setAttribute('color', new THREE.Float32BufferAttribute(colors2, 3));
+    geometry3.setAttribute('color', new THREE.Float32BufferAttribute(colors3, 3));
+
+    //
+
+    const geometry4 = new THREE.BufferGeometry();
+    const geometry5 = new THREE.BufferGeometry();
+    const geometry6 = new THREE.BufferGeometry();
+
+    vertices = [];
+    colors1 = [];
+    colors2 = [];
+    colors3 = [];
+
+    for (let i = 0; i < hilbertPoints.length; i++) {
+        const point = hilbertPoints[i];
+
+        vertices.push(point.x, point.y, point.z);
+
+        color.setHSL(0.6, 1.0, Math.max(0, (200 - hilbertPoints[i].x) / 400) * 0.5 + 0.5, THREE.SRGBColorSpace);
+        colors1.push(color.r, color.g, color.b);
+
+        color.setHSL(0.3, 1.0, Math.max(0, (200 + hilbertPoints[i].x) / 400) * 0.5, THREE.SRGBColorSpace);
+        colors2.push(color.r, color.g, color.b);
+
+        color.setHSL(i / hilbertPoints.length, 1.0, 0.5, THREE.SRGBColorSpace);
+        colors3.push(color.r, color.g, color.b);
+    }
+
+    geometry4.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry5.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+    geometry6.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    geometry4.setAttribute('color', new THREE.Float32BufferAttribute(colors1, 3));
+    geometry5.setAttribute('color', new THREE.Float32BufferAttribute(colors2, 3));
+    geometry6.setAttribute('color', new THREE.Float32BufferAttribute(colors3, 3));
+
+    // Create lines and add to scene
+
+    const material = new THREE.LineBasicMaterial({ color: 0xffffff, vertexColors: true });
+
+    let line, p;
+    const scale = 0.3,
+        d = 225;
+
+    const parameters = [
+        [material, scale * 1.5, [-d, -d / 2, 0], geometry1],
+        [material, scale * 1.5, [0, -d / 2, 0], geometry2],
+        [material, scale * 1.5, [d, -d / 2, 0], geometry3],
+
+        [material, scale * 1.5, [-d, d / 2, 0], geometry4],
+        [material, scale * 1.5, [0, d / 2, 0], geometry5],
+        [material, scale * 1.5, [d, d / 2, 0], geometry6],
+    ];
+
+    for (let i = 0; i < parameters.length; i++) {
+        p = parameters[i];
+        line = new THREE.Line(p[3], p[0]);
+        line.scale.x = line.scale.y = line.scale.z = p[1];
+        line.position.x = p[2][0];
+        line.position.y = p[2][1];
+        line.position.z = p[2][2];
+        scene.add(line);
+    }
+
+    //
+
+    document.body.style.touchAction = 'none';
+    document.body.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY + 200 - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    const time = Date.now() * 0.0005;
+
+    for (let i = 0; i < scene.children.length; i++) {
+        const object = scene.children[i];
+
+        if (object.isLine) {
+            object.rotation.y = time * (i % 2 ? 1 : -1);
+        }
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lines_dashed.ts b/examples-testing/examples/webgl_lines_dashed.ts
new file mode 100644
index 000000000..4849e7c3a
--- /dev/null
+++ b/examples-testing/examples/webgl_lines_dashed.ts
@@ -0,0 +1,186 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
+
+let renderer, scene, camera, stats;
+const objects = [];
+
+const WIDTH = window.innerWidth,
+    HEIGHT = window.innerHeight;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, WIDTH / HEIGHT, 1, 200);
+    camera.position.z = 150;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x111111);
+    scene.fog = new THREE.Fog(0x111111, 150, 200);
+
+    const subdivisions = 6;
+    const recursion = 1;
+
+    const points = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 25.0, recursion, 0, 1, 2, 3, 4, 5, 6, 7);
+    const spline = new THREE.CatmullRomCurve3(points);
+
+    const samples = spline.getPoints(points.length * subdivisions);
+    const geometrySpline = new THREE.BufferGeometry().setFromPoints(samples);
+
+    const line = new THREE.Line(
+        geometrySpline,
+        new THREE.LineDashedMaterial({ color: 0xffffff, dashSize: 1, gapSize: 0.5 }),
+    );
+    line.computeLineDistances();
+
+    objects.push(line);
+    scene.add(line);
+
+    const geometryBox = box(50, 50, 50);
+
+    const lineSegments = new THREE.LineSegments(
+        geometryBox,
+        new THREE.LineDashedMaterial({ color: 0xffaa00, dashSize: 3, gapSize: 1 }),
+    );
+    lineSegments.computeLineDistances();
+
+    objects.push(lineSegments);
+    scene.add(lineSegments);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(WIDTH, HEIGHT);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function box(width, height, depth) {
+    (width = width * 0.5), (height = height * 0.5), (depth = depth * 0.5);
+
+    const geometry = new THREE.BufferGeometry();
+    const position = [];
+
+    position.push(
+        -width,
+        -height,
+        -depth,
+        -width,
+        height,
+        -depth,
+
+        -width,
+        height,
+        -depth,
+        width,
+        height,
+        -depth,
+
+        width,
+        height,
+        -depth,
+        width,
+        -height,
+        -depth,
+
+        width,
+        -height,
+        -depth,
+        -width,
+        -height,
+        -depth,
+
+        -width,
+        -height,
+        depth,
+        -width,
+        height,
+        depth,
+
+        -width,
+        height,
+        depth,
+        width,
+        height,
+        depth,
+
+        width,
+        height,
+        depth,
+        width,
+        -height,
+        depth,
+
+        width,
+        -height,
+        depth,
+        -width,
+        -height,
+        depth,
+
+        -width,
+        -height,
+        -depth,
+        -width,
+        -height,
+        depth,
+
+        -width,
+        height,
+        -depth,
+        -width,
+        height,
+        depth,
+
+        width,
+        height,
+        -depth,
+        width,
+        height,
+        depth,
+
+        width,
+        -height,
+        -depth,
+        width,
+        -height,
+        depth,
+    );
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
+
+    return geometry;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.001;
+
+    scene.traverse(function (object) {
+        if (object.isLine) {
+            object.rotation.x = 0.25 * time;
+            object.rotation.y = 0.25 * time;
+        }
+    });
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lines_fat.ts b/examples-testing/examples/webgl_lines_fat.ts
new file mode 100644
index 000000000..34c2e2d5e
--- /dev/null
+++ b/examples-testing/examples/webgl_lines_fat.ts
@@ -0,0 +1,251 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Line2 } from 'three/addons/lines/Line2.js';
+import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
+import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
+import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
+
+let line, renderer, scene, camera, camera2, controls;
+let line1;
+let matLine, matLineBasic, matLineDashed;
+let stats, gpuPanel;
+let gui;
+
+// viewport
+let insetWidth;
+let insetHeight;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setClearColor(0x000000, 0.0);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(-40, 0, 60);
+
+    camera2 = new THREE.PerspectiveCamera(40, 1, 1, 1000);
+    camera2.position.copy(camera.position);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 10;
+    controls.maxDistance = 500;
+
+    // Position and THREE.Color Data
+
+    const positions = [];
+    const colors = [];
+
+    const points = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 20.0, 1, 0, 1, 2, 3, 4, 5, 6, 7);
+
+    const spline = new THREE.CatmullRomCurve3(points);
+    const divisions = Math.round(12 * points.length);
+    const point = new THREE.Vector3();
+    const color = new THREE.Color();
+
+    for (let i = 0, l = divisions; i < l; i++) {
+        const t = i / l;
+
+        spline.getPoint(t, point);
+        positions.push(point.x, point.y, point.z);
+
+        color.setHSL(t, 1.0, 0.5, THREE.SRGBColorSpace);
+        colors.push(color.r, color.g, color.b);
+    }
+
+    // Line2 ( LineGeometry, LineMaterial )
+
+    const geometry = new LineGeometry();
+    geometry.setPositions(positions);
+    geometry.setColors(colors);
+
+    matLine = new LineMaterial({
+        color: 0xffffff,
+        linewidth: 5, // in world units with size attenuation, pixels otherwise
+        vertexColors: true,
+
+        dashed: false,
+        alphaToCoverage: true,
+    });
+
+    line = new Line2(geometry, matLine);
+    line.computeLineDistances();
+    line.scale.set(1, 1, 1);
+    scene.add(line);
+
+    // THREE.Line ( THREE.BufferGeometry, THREE.LineBasicMaterial ) - rendered with gl.LINE_STRIP
+
+    const geo = new THREE.BufferGeometry();
+    geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    matLineBasic = new THREE.LineBasicMaterial({ vertexColors: true });
+    matLineDashed = new THREE.LineDashedMaterial({ vertexColors: true, scale: 2, dashSize: 1, gapSize: 1 });
+
+    line1 = new THREE.Line(geo, matLineBasic);
+    line1.computeLineDistances();
+    line1.visible = false;
+    scene.add(line1);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    onWindowResize();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    gpuPanel = new GPUStatsPanel(renderer.getContext());
+    stats.addPanel(gpuPanel);
+    stats.showPanel(0);
+
+    initGui();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    insetWidth = window.innerHeight / 4; // square
+    insetHeight = window.innerHeight / 4;
+
+    camera2.aspect = insetWidth / insetHeight;
+    camera2.updateProjectionMatrix();
+}
+
+function animate() {
+    // main scene
+
+    renderer.setClearColor(0x000000, 0);
+
+    renderer.setViewport(0, 0, window.innerWidth, window.innerHeight);
+
+    controls.update();
+
+    gpuPanel.startQuery();
+    renderer.render(scene, camera);
+    gpuPanel.endQuery();
+
+    // inset scene
+
+    renderer.setClearColor(0x222222, 1);
+
+    renderer.clearDepth(); // important!
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(20, 20, insetWidth, insetHeight);
+
+    renderer.setViewport(20, 20, insetWidth, insetHeight);
+
+    camera2.position.copy(camera.position);
+    camera2.quaternion.copy(camera.quaternion);
+
+    renderer.render(scene, camera2);
+
+    renderer.setScissorTest(false);
+
+    stats.update();
+}
+
+//
+
+function initGui() {
+    gui = new GUI();
+
+    const param = {
+        'line type': 0,
+        'world units': false,
+        width: 5,
+        alphaToCoverage: true,
+        dashed: false,
+        'dash scale': 1,
+        'dash / gap': 1,
+    };
+
+    gui.add(param, 'line type', { LineGeometry: 0, 'gl.LINE': 1 }).onChange(function (val) {
+        switch (val) {
+            case 0:
+                line.visible = true;
+
+                line1.visible = false;
+
+                break;
+
+            case 1:
+                line.visible = false;
+
+                line1.visible = true;
+
+                break;
+        }
+    });
+
+    gui.add(param, 'world units').onChange(function (val) {
+        matLine.worldUnits = val;
+        matLine.needsUpdate = true;
+    });
+
+    gui.add(param, 'width', 1, 10).onChange(function (val) {
+        matLine.linewidth = val;
+    });
+
+    gui.add(param, 'alphaToCoverage').onChange(function (val) {
+        matLine.alphaToCoverage = val;
+    });
+
+    gui.add(param, 'dashed').onChange(function (val) {
+        matLine.dashed = val;
+        line1.material = val ? matLineDashed : matLineBasic;
+    });
+
+    gui.add(param, 'dash scale', 0.5, 2, 0.1).onChange(function (val) {
+        matLine.dashScale = val;
+        matLineDashed.scale = val;
+    });
+
+    gui.add(param, 'dash / gap', { '2 : 1': 0, '1 : 1': 1, '1 : 2': 2 }).onChange(function (val) {
+        switch (val) {
+            case 0:
+                matLine.dashSize = 2;
+                matLine.gapSize = 1;
+
+                matLineDashed.dashSize = 2;
+                matLineDashed.gapSize = 1;
+
+                break;
+
+            case 1:
+                matLine.dashSize = 1;
+                matLine.gapSize = 1;
+
+                matLineDashed.dashSize = 1;
+                matLineDashed.gapSize = 1;
+
+                break;
+
+            case 2:
+                matLine.dashSize = 1;
+                matLine.gapSize = 2;
+
+                matLineDashed.dashSize = 1;
+                matLineDashed.gapSize = 2;
+
+                break;
+        }
+    });
+}
diff --git a/examples-testing/examples/webgl_lines_fat_raycasting.ts b/examples-testing/examples/webgl_lines_fat_raycasting.ts
new file mode 100644
index 000000000..75cef6204
--- /dev/null
+++ b/examples-testing/examples/webgl_lines_fat_raycasting.ts
@@ -0,0 +1,294 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
+import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
+import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
+import { Line2 } from 'three/addons/lines/Line2.js';
+import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
+
+let line, thresholdLine, segments, thresholdSegments;
+let renderer, scene, camera, controls;
+let sphereInter, sphereOnLine;
+let stats, gpuPanel;
+let gui;
+let clock;
+
+const color = new THREE.Color();
+
+const pointer = new THREE.Vector2(Infinity, Infinity);
+
+const raycaster = new THREE.Raycaster();
+
+raycaster.params.Line2 = {};
+raycaster.params.Line2.threshold = 0;
+
+const matLine = new LineMaterial({
+    color: 0xffffff,
+    linewidth: 1, // in world units with size attenuation, pixels otherwise
+    worldUnits: true,
+    vertexColors: true,
+
+    alphaToCoverage: true,
+});
+
+const matThresholdLine = new LineMaterial({
+    color: 0xffffff,
+    linewidth: matLine.linewidth, // in world units with size attenuation, pixels otherwise
+    worldUnits: true,
+    // vertexColors: true,
+    transparent: true,
+    opacity: 0.2,
+    depthTest: false,
+    visible: false,
+});
+
+const params = {
+    'line type': 0,
+    'world units': matLine.worldUnits,
+    'visualize threshold': matThresholdLine.visible,
+    width: matLine.linewidth,
+    alphaToCoverage: matLine.alphaToCoverage,
+    threshold: raycaster.params.Line2.threshold,
+    translation: raycaster.params.Line2.threshold,
+    animate: true,
+};
+
+init();
+
+function init() {
+    clock = new THREE.Clock();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setClearColor(0x000000, 0.0);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(-40, 0, 60);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 10;
+    controls.maxDistance = 500;
+
+    const sphereGeometry = new THREE.SphereGeometry(0.25, 8, 4);
+    const sphereInterMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, depthTest: false });
+    const sphereOnLineMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, depthTest: false });
+
+    sphereInter = new THREE.Mesh(sphereGeometry, sphereInterMaterial);
+    sphereOnLine = new THREE.Mesh(sphereGeometry, sphereOnLineMaterial);
+    sphereInter.visible = false;
+    sphereOnLine.visible = false;
+    sphereInter.renderOrder = 10;
+    sphereOnLine.renderOrder = 10;
+    scene.add(sphereInter);
+    scene.add(sphereOnLine);
+
+    // Position and THREE.Color Data
+
+    const positions = [];
+    const colors = [];
+    const points = [];
+    for (let i = -50; i < 50; i++) {
+        const t = i / 3;
+        points.push(new THREE.Vector3(t * Math.sin(2 * t), t, t * Math.cos(2 * t)));
+    }
+
+    const spline = new THREE.CatmullRomCurve3(points);
+    const divisions = Math.round(3 * points.length);
+    const point = new THREE.Vector3();
+    const color = new THREE.Color();
+
+    for (let i = 0, l = divisions; i < l; i++) {
+        const t = i / l;
+
+        spline.getPoint(t, point);
+        positions.push(point.x, point.y, point.z);
+
+        color.setHSL(t, 1.0, 0.5, THREE.SRGBColorSpace);
+        colors.push(color.r, color.g, color.b);
+    }
+
+    const lineGeometry = new LineGeometry();
+    lineGeometry.setPositions(positions);
+    lineGeometry.setColors(colors);
+
+    const segmentsGeometry = new LineSegmentsGeometry();
+    segmentsGeometry.setPositions(positions);
+    segmentsGeometry.setColors(colors);
+
+    segments = new LineSegments2(segmentsGeometry, matLine);
+    segments.computeLineDistances();
+    segments.scale.set(1, 1, 1);
+    scene.add(segments);
+    segments.visible = false;
+
+    thresholdSegments = new LineSegments2(segmentsGeometry, matThresholdLine);
+    thresholdSegments.computeLineDistances();
+    thresholdSegments.scale.set(1, 1, 1);
+    scene.add(thresholdSegments);
+    thresholdSegments.visible = false;
+
+    line = new Line2(lineGeometry, matLine);
+    line.computeLineDistances();
+    line.scale.set(1, 1, 1);
+    scene.add(line);
+
+    thresholdLine = new Line2(lineGeometry, matThresholdLine);
+    thresholdLine.computeLineDistances();
+    thresholdLine.scale.set(1, 1, 1);
+    scene.add(thresholdLine);
+
+    const geo = new THREE.BufferGeometry();
+    geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
+    geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+
+    //
+
+    document.addEventListener('pointermove', onPointerMove);
+    window.addEventListener('resize', onWindowResize);
+    onWindowResize();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    gpuPanel = new GPUStatsPanel(renderer.getContext());
+    stats.addPanel(gpuPanel);
+    stats.showPanel(0);
+    initGui();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    const obj = line.visible ? line : segments;
+    thresholdLine.position.copy(line.position);
+    thresholdLine.quaternion.copy(line.quaternion);
+    thresholdSegments.position.copy(segments.position);
+    thresholdSegments.quaternion.copy(segments.quaternion);
+
+    if (params.animate) {
+        line.rotation.y += delta * 0.1;
+
+        segments.rotation.y = line.rotation.y;
+    }
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObject(obj);
+
+    if (intersects.length > 0) {
+        sphereInter.visible = true;
+        sphereOnLine.visible = true;
+
+        sphereInter.position.copy(intersects[0].point);
+        sphereOnLine.position.copy(intersects[0].pointOnLine);
+
+        const index = intersects[0].faceIndex;
+        const colors = obj.geometry.getAttribute('instanceColorStart');
+
+        color.fromBufferAttribute(colors, index);
+
+        sphereInter.material.color.copy(color).offsetHSL(0.3, 0, 0);
+        sphereOnLine.material.color.copy(color).offsetHSL(0.7, 0, 0);
+
+        renderer.domElement.style.cursor = 'crosshair';
+    } else {
+        sphereInter.visible = false;
+        sphereOnLine.visible = false;
+        renderer.domElement.style.cursor = '';
+    }
+
+    gpuPanel.startQuery();
+    renderer.render(scene, camera);
+    gpuPanel.endQuery();
+
+    stats.update();
+}
+
+//
+
+function switchLine(val) {
+    switch (val) {
+        case 0:
+            line.visible = true;
+            thresholdLine.visible = true;
+
+            segments.visible = false;
+            thresholdSegments.visible = false;
+
+            break;
+
+        case 1:
+            line.visible = false;
+            thresholdLine.visible = false;
+
+            segments.visible = true;
+            thresholdSegments.visible = true;
+
+            break;
+    }
+}
+
+function initGui() {
+    gui = new GUI();
+
+    gui.add(params, 'line type', { LineGeometry: 0, LineSegmentsGeometry: 1 })
+        .onChange(function (val) {
+            switchLine(val);
+        })
+        .setValue(1);
+
+    gui.add(params, 'world units').onChange(function (val) {
+        matLine.worldUnits = val;
+        matLine.needsUpdate = true;
+
+        matThresholdLine.worldUnits = val;
+        matThresholdLine.needsUpdate = true;
+    });
+
+    gui.add(params, 'visualize threshold').onChange(function (val) {
+        matThresholdLine.visible = val;
+    });
+
+    gui.add(params, 'width', 1, 10).onChange(function (val) {
+        matLine.linewidth = val;
+        matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
+    });
+
+    gui.add(params, 'alphaToCoverage').onChange(function (val) {
+        matLine.alphaToCoverage = val;
+    });
+
+    gui.add(params, 'threshold', 0, 10).onChange(function (val) {
+        raycaster.params.Line2.threshold = val;
+        matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
+    });
+
+    gui.add(params, 'translation', 0, 10).onChange(function (val) {
+        line.position.x = val;
+        segments.position.x = val;
+    });
+
+    gui.add(params, 'animate');
+}
diff --git a/examples-testing/examples/webgl_lines_fat_wireframe.ts b/examples-testing/examples/webgl_lines_fat_wireframe.ts
new file mode 100644
index 000000000..59660ad7e
--- /dev/null
+++ b/examples-testing/examples/webgl_lines_fat_wireframe.ts
@@ -0,0 +1,210 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
+import { Wireframe } from 'three/addons/lines/Wireframe.js';
+import { WireframeGeometry2 } from 'three/addons/lines/WireframeGeometry2.js';
+
+let wireframe, renderer, scene, camera, camera2, controls;
+let wireframe1;
+let matLine, matLineBasic, matLineDashed;
+let stats;
+let gui;
+
+// viewport
+let insetWidth;
+let insetHeight;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setClearColor(0x000000, 0.0);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(-50, 0, 50);
+
+    camera2 = new THREE.PerspectiveCamera(40, 1, 1, 1000);
+    camera2.position.copy(camera.position);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 10;
+    controls.maxDistance = 500;
+
+    // Wireframe ( WireframeGeometry2, LineMaterial )
+
+    let geo = new THREE.IcosahedronGeometry(20, 1);
+
+    const geometry = new WireframeGeometry2(geo);
+
+    matLine = new LineMaterial({
+        color: 0x4080ff,
+        linewidth: 5, // in pixels
+        dashed: false,
+    });
+
+    wireframe = new Wireframe(geometry, matLine);
+    wireframe.computeLineDistances();
+    wireframe.scale.set(1, 1, 1);
+    scene.add(wireframe);
+
+    // Line ( THREE.WireframeGeometry, THREE.LineBasicMaterial ) - rendered with gl.LINE
+
+    geo = new THREE.WireframeGeometry(geo);
+
+    matLineBasic = new THREE.LineBasicMaterial({ color: 0x4080ff });
+    matLineDashed = new THREE.LineDashedMaterial({ scale: 2, dashSize: 1, gapSize: 1 });
+
+    wireframe1 = new THREE.LineSegments(geo, matLineBasic);
+    wireframe1.computeLineDistances();
+    wireframe1.visible = false;
+    scene.add(wireframe1);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    onWindowResize();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    initGui();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    insetWidth = window.innerHeight / 4; // square
+    insetHeight = window.innerHeight / 4;
+
+    camera2.aspect = insetWidth / insetHeight;
+    camera2.updateProjectionMatrix();
+}
+
+function animate() {
+    // main scene
+
+    renderer.setClearColor(0x000000, 0);
+
+    renderer.setViewport(0, 0, window.innerWidth, window.innerHeight);
+
+    renderer.render(scene, camera);
+
+    // inset scene
+
+    renderer.setClearColor(0x222222, 1);
+
+    renderer.clearDepth(); // important!
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(20, 20, insetWidth, insetHeight);
+
+    renderer.setViewport(20, 20, insetWidth, insetHeight);
+
+    camera2.position.copy(camera.position);
+    camera2.quaternion.copy(camera.quaternion);
+
+    renderer.render(scene, camera2);
+
+    renderer.setScissorTest(false);
+
+    stats.update();
+}
+
+//
+
+function initGui() {
+    gui = new GUI();
+
+    const param = {
+        'line type': 0,
+        'width (px)': 5,
+        dashed: false,
+        'dash scale': 1,
+        'dash / gap': 1,
+    };
+
+    gui.add(param, 'line type', { LineGeometry: 0, 'gl.LINE': 1 }).onChange(function (val) {
+        switch (val) {
+            case 0:
+                wireframe.visible = true;
+
+                wireframe1.visible = false;
+
+                break;
+
+            case 1:
+                wireframe.visible = false;
+
+                wireframe1.visible = true;
+
+                break;
+        }
+    });
+
+    gui.add(param, 'width (px)', 1, 10).onChange(function (val) {
+        matLine.linewidth = val;
+    });
+
+    gui.add(param, 'dashed').onChange(function (val) {
+        matLine.dashed = val;
+
+        // dashed is implemented as a defines -- not as a uniform. this could be changed.
+        // ... or THREE.LineDashedMaterial could be implemented as a separate material
+        // temporary hack - renderer should do this eventually
+        if (val) matLine.defines.USE_DASH = '';
+        else delete matLine.defines.USE_DASH;
+        matLine.needsUpdate = true;
+
+        wireframe1.material = val ? matLineDashed : matLineBasic;
+    });
+
+    gui.add(param, 'dash scale', 0.5, 1, 0.1).onChange(function (val) {
+        matLine.dashScale = val;
+        matLineDashed.scale = val;
+    });
+
+    gui.add(param, 'dash / gap', { '2 : 1': 0, '1 : 1': 1, '1 : 2': 2 }).onChange(function (val) {
+        switch (val) {
+            case 0:
+                matLine.dashSize = 2;
+                matLine.gapSize = 1;
+
+                matLineDashed.dashSize = 2;
+                matLineDashed.gapSize = 1;
+
+                break;
+
+            case 1:
+                matLine.dashSize = 1;
+                matLine.gapSize = 1;
+
+                matLineDashed.dashSize = 1;
+                matLineDashed.gapSize = 1;
+
+                break;
+
+            case 2:
+                matLine.dashSize = 1;
+                matLine.gapSize = 2;
+
+                matLineDashed.dashSize = 1;
+                matLineDashed.gapSize = 2;
+
+                break;
+        }
+    });
+}
diff --git a/examples-testing/examples/webgl_loader_3dm.ts b/examples-testing/examples/webgl_loader_3dm.ts
new file mode 100644
index 000000000..7570306fd
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_3dm.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+let controls, gui;
+
+init();
+
+function init() {
+    THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(26, -40, 5);
+
+    scene = new THREE.Scene();
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 6);
+    directionalLight.position.set(0, 0, 2);
+    scene.add(directionalLight);
+
+    const loader = new Rhino3dmLoader();
+    //generally, use this for the Library Path: https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1
+    loader.setLibraryPath('jsm/libs/rhino3dm/');
+    loader.load(
+        'models/3dm/Rhino_Logo.3dm',
+        function (object) {
+            scene.add(object);
+            initGUI(object.userData.layers);
+
+            // hide spinner
+            document.getElementById('loader').style.display = 'none';
+        },
+        function (progress) {
+            console.log((progress.loaded / progress.total) * 100 + '%');
+        },
+        function (error) {
+            console.log(error);
+        },
+    );
+
+    controls = new OrbitControls(camera, renderer.domElement);
+
+    window.addEventListener('resize', resize);
+}
+
+function resize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    controls.update();
+    renderer.render(scene, camera);
+}
+
+function initGUI(layers) {
+    gui = new GUI({ title: 'layers' });
+
+    for (let i = 0; i < layers.length; i++) {
+        const layer = layers[i];
+        gui.add(layer, 'visible')
+            .name(layer.name)
+            .onChange(function (val) {
+                const name = this.object.name;
+
+                scene.traverse(function (child) {
+                    if (child.userData.hasOwnProperty('attributes')) {
+                        if ('layerIndex' in child.userData.attributes) {
+                            const layerName = layers[child.userData.attributes.layerIndex].name;
+
+                            if (layerName === name) {
+                                child.visible = val;
+                                layer.visible = val;
+                            }
+                        }
+                    }
+                });
+            });
+    }
+}
diff --git a/examples-testing/examples/webgl_loader_3ds.ts b/examples-testing/examples/webgl_loader_3ds.ts
new file mode 100644
index 000000000..10ce34076
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_3ds.ts
@@ -0,0 +1,62 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';
+
+let container, controls;
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+    scene.add(new THREE.AmbientLight(0xffffff, 3));
+
+    const directionalLight = new THREE.DirectionalLight(0xffeedd, 3);
+    directionalLight.position.set(0, 0, 2);
+    scene.add(directionalLight);
+
+    //3ds files dont store normal maps
+    const normal = new THREE.TextureLoader().load('models/3ds/portalgun/textures/normal.jpg');
+
+    const loader = new TDSLoader();
+    loader.setResourcePath('models/3ds/portalgun/textures/');
+    loader.load('models/3ds/portalgun/portalgun.3ds', function (object) {
+        object.traverse(function (child) {
+            if (child.isMesh) {
+                child.material.specular.setScalar(0.1);
+                child.material.normalMap = normal;
+            }
+        });
+
+        scene.add(object);
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    controls = new TrackballControls(camera, renderer.domElement);
+
+    window.addEventListener('resize', resize);
+}
+
+function resize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_3mf.ts b/examples-testing/examples/webgl_loader_3mf.ts
new file mode 100644
index 000000000..c31e32196
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_3mf.ts
@@ -0,0 +1,105 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, object, loader, controls;
+
+const params = {
+    asset: 'cube_gears',
+};
+
+const assets = ['cube_gears', 'facecolors', 'multipletextures', 'vertexcolors'];
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x333333);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
+
+    // Z is up for objects intended to be 3D printed.
+
+    camera.up.set(0, 0, 1);
+    camera.position.set(-100, -250, 100);
+    scene.add(camera);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 50;
+    controls.maxDistance = 400;
+    controls.enablePan = false;
+    controls.update();
+
+    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
+
+    const light = new THREE.DirectionalLight(0xffffff, 2);
+    light.position.set(-1, -2.5, 1);
+    scene.add(light);
+
+    const manager = new THREE.LoadingManager();
+
+    manager.onLoad = function () {
+        const aabb = new THREE.Box3().setFromObject(object);
+        const center = aabb.getCenter(new THREE.Vector3());
+
+        object.position.x += object.position.x - center.x;
+        object.position.y += object.position.y - center.y;
+        object.position.z += object.position.z - center.z;
+
+        controls.reset();
+
+        scene.add(object);
+        render();
+    };
+
+    loader = new ThreeMFLoader(manager);
+    loadAsset(params.asset);
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'asset', assets).onChange(function (value) {
+        loadAsset(value);
+    });
+}
+
+function loadAsset(asset) {
+    loader.load('models/3mf/' + asset + '.3mf', function (group) {
+        if (object) {
+            object.traverse(function (child) {
+                if (child.material) child.material.dispose();
+                if (child.material && child.material.map) child.material.map.dispose();
+                if (child.geometry) child.geometry.dispose();
+            });
+
+            scene.remove(object);
+        }
+
+        //
+
+        object = group;
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_3mf_materials.ts b/examples-testing/examples/webgl_loader_3mf_materials.ts
new file mode 100644
index 000000000..fcdd7308e
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_3mf_materials.ts
@@ -0,0 +1,106 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 10, 500);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.set(-50, 40, 50);
+    scene.add(camera);
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
+    hemiLight.position.set(0, 100, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(-0, 40, 50);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.top = 50;
+    dirLight.shadow.camera.bottom = -25;
+    dirLight.shadow.camera.left = -25;
+    dirLight.shadow.camera.right = 25;
+    dirLight.shadow.camera.near = 0.1;
+    dirLight.shadow.camera.far = 200;
+    dirLight.shadow.mapSize.set(1024, 1024);
+    scene.add(dirLight);
+
+    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
+
+    //
+
+    const manager = new THREE.LoadingManager();
+
+    const loader = new ThreeMFLoader(manager);
+    loader.load('./models/3mf/truck.3mf', function (object) {
+        object.rotation.set(-Math.PI / 2, 0, 0); // z-up conversion
+
+        object.traverse(function (child) {
+            child.castShadow = true;
+        });
+
+        scene.add(object);
+    });
+
+    //
+
+    manager.onLoad = function () {
+        render();
+    };
+
+    //
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(1000, 1000),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
+    );
+    ground.rotation.x = -Math.PI / 2;
+    ground.position.y = 11;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 50;
+    controls.maxDistance = 200;
+    controls.enablePan = false;
+    controls.target.set(0, 20, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+
+    render();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_amf.ts b/examples-testing/examples/webgl_loader_amf.ts
new file mode 100644
index 000000000..ee576e04f
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_amf.ts
@@ -0,0 +1,62 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x999999);
+
+    scene.add(new THREE.AmbientLight(0x999999));
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
+
+    // Z is up for objects intended to be 3D printed.
+
+    camera.up.set(0, 0, 1);
+    camera.position.set(0, -9, 6);
+
+    camera.add(new THREE.PointLight(0xffffff, 250));
+
+    scene.add(camera);
+
+    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x555555);
+    grid.rotateOnAxis(new THREE.Vector3(1, 0, 0), 90 * (Math.PI / 180));
+    scene.add(grid);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const loader = new AMFLoader();
+    loader.load('./models/amf/rook.amf', function (amfobject) {
+        scene.add(amfobject);
+        render();
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.target.set(0, 0, 2);
+    controls.enableZoom = false;
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_bvh.ts b/examples-testing/examples/webgl_loader_bvh.ts
new file mode 100644
index 000000000..0be3add4d
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_bvh.ts
@@ -0,0 +1,61 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';
+
+const clock = new THREE.Clock();
+
+let camera, controls, scene, renderer;
+let mixer;
+
+init();
+
+const loader = new BVHLoader();
+loader.load('models/bvh/pirouette.bvh', function (result) {
+    const skeletonHelper = new THREE.SkeletonHelper(result.skeleton.bones[0]);
+
+    scene.add(result.skeleton.bones[0]);
+    scene.add(skeletonHelper);
+
+    // play animation
+    mixer = new THREE.AnimationMixer(result.skeleton.bones[0]);
+    mixer.clipAction(result.clip).play();
+});
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 200, 300);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xeeeeee);
+
+    scene.add(new THREE.GridHelper(400, 10));
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 300;
+    controls.maxDistance = 700;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) mixer.update(delta);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_collada.ts b/examples-testing/examples/webgl_loader_collada.ts
new file mode 100644
index 000000000..62588b698
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_collada.ts
@@ -0,0 +1,83 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';
+
+let container, stats, clock;
+let camera, scene, renderer, elf;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
+    camera.position.set(8, 10, 8);
+    camera.lookAt(0, 3, 0);
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    // loading manager
+
+    const loadingManager = new THREE.LoadingManager(function () {
+        scene.add(elf);
+    });
+
+    // collada
+
+    const loader = new ColladaLoader(loadingManager);
+    loader.load('./models/collada/elf/elf.dae', function (collada) {
+        elf = collada.scene;
+    });
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
+    directionalLight.position.set(1, 1, 0).normalize();
+    scene.add(directionalLight);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    if (elf !== undefined) {
+        elf.rotation.z += delta * 0.5;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_collada_skinning.ts b/examples-testing/examples/webgl_loader_collada_skinning.ts
new file mode 100644
index 000000000..5cb808b17
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_collada_skinning.ts
@@ -0,0 +1,97 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, stats, clock, controls;
+let camera, scene, renderer, mixer;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(15, 10, -15);
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    // collada
+
+    const loader = new ColladaLoader();
+    loader.load('./models/collada/stormtrooper/stormtrooper.dae', function (collada) {
+        const avatar = collada.scene;
+        const animations = avatar.animations;
+
+        mixer = new THREE.AnimationMixer(avatar);
+        mixer.clipAction(animations[0]).play();
+
+        scene.add(avatar);
+    });
+
+    //
+
+    const gridHelper = new THREE.GridHelper(10, 20, 0xc1c1c1, 0x8d8d8d);
+    scene.add(gridHelper);
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(1.5, 1, -1.5);
+    scene.add(directionalLight);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.screenSpacePanning = true;
+    controls.minDistance = 5;
+    controls.maxDistance = 40;
+    controls.target.set(0, 2, 0);
+    controls.update();
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    if (mixer !== undefined) {
+        mixer.update(delta);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_draco.ts b/examples-testing/examples/webgl_loader_draco.ts
new file mode 100644
index 000000000..c9947c693
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_draco.ts
@@ -0,0 +1,85 @@
+import * as THREE from 'three';
+
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+let camera, scene, renderer;
+
+const container = document.querySelector('#container');
+
+// Configure and create Draco decoder.
+const dracoLoader = new DRACOLoader();
+dracoLoader.setDecoderPath('jsm/libs/draco/');
+dracoLoader.setDecoderConfig({ type: 'js' });
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);
+    camera.position.set(3, 0.25, 3);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x443333);
+    scene.fog = new THREE.Fog(0x443333, 1, 4);
+
+    // Ground
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(8, 8),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, specular: 0x101010 }),
+    );
+    plane.rotation.x = -Math.PI / 2;
+    plane.position.y = 0.03;
+    plane.receiveShadow = true;
+    scene.add(plane);
+
+    // Lights
+    const hemiLight = new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3);
+    scene.add(hemiLight);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.intensity = 7;
+    spotLight.angle = Math.PI / 16;
+    spotLight.penumbra = 0.5;
+    spotLight.castShadow = true;
+    spotLight.position.set(-1, 1, 1);
+    scene.add(spotLight);
+
+    dracoLoader.load('models/draco/bunny.drc', function (geometry) {
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshStandardMaterial({ color: 0xa5a5a5 });
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+        scene.add(mesh);
+
+        // Release decoder resources.
+        dracoLoader.dispose();
+    });
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const timer = Date.now() * 0.0003;
+
+    camera.position.x = Math.sin(timer) * 0.5;
+    camera.position.z = Math.cos(timer) * 0.5;
+    camera.lookAt(0, 0.1, 0);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_fbx.ts b/examples-testing/examples/webgl_loader_fbx.ts
new file mode 100644
index 000000000..3b157a222
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_fbx.ts
@@ -0,0 +1,162 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const manager = new THREE.LoadingManager();
+
+let camera, scene, renderer, stats, object, loader, guiMorphsFolder;
+let mixer;
+
+const clock = new THREE.Clock();
+
+const params = {
+    asset: 'Samba Dancing',
+};
+
+const assets = ['Samba Dancing', 'morph_test'];
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(100, 200, 300);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+    scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 5);
+    hemiLight.position.set(0, 200, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 5);
+    dirLight.position.set(0, 200, 100);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.top = 180;
+    dirLight.shadow.camera.bottom = -100;
+    dirLight.shadow.camera.left = -120;
+    dirLight.shadow.camera.right = 120;
+    scene.add(dirLight);
+
+    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
+
+    // ground
+    const mesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(2000, 2000),
+        new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }),
+    );
+    mesh.rotation.x = -Math.PI / 2;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    const grid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
+    grid.material.opacity = 0.2;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    loader = new FBXLoader(manager);
+    loadAsset(params.asset);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 100, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+
+    // stats
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const gui = new GUI();
+    gui.add(params, 'asset', assets).onChange(function (value) {
+        loadAsset(value);
+    });
+
+    guiMorphsFolder = gui.addFolder('Morphs').hide();
+}
+
+function loadAsset(asset) {
+    loader.load('models/fbx/' + asset + '.fbx', function (group) {
+        if (object) {
+            object.traverse(function (child) {
+                if (child.material) {
+                    const materials = Array.isArray(child.material) ? child.material : [child.material];
+                    materials.forEach(material => {
+                        if (material.map) material.map.dispose();
+                        material.dispose();
+                    });
+                }
+
+                if (child.geometry) child.geometry.dispose();
+            });
+
+            scene.remove(object);
+        }
+
+        //
+
+        object = group;
+
+        if (object.animations && object.animations.length) {
+            mixer = new THREE.AnimationMixer(object);
+
+            const action = mixer.clipAction(object.animations[0]);
+            action.play();
+        } else {
+            mixer = null;
+        }
+
+        guiMorphsFolder.children.forEach(child => child.destroy());
+        guiMorphsFolder.hide();
+
+        object.traverse(function (child) {
+            if (child.isMesh) {
+                child.castShadow = true;
+                child.receiveShadow = true;
+
+                if (child.morphTargetDictionary) {
+                    guiMorphsFolder.show();
+                    const meshFolder = guiMorphsFolder.addFolder(child.name || child.uuid);
+                    Object.keys(child.morphTargetDictionary).forEach(key => {
+                        meshFolder.add(child.morphTargetInfluences, child.morphTargetDictionary[key], 0, 1, 0.01);
+                    });
+                }
+            }
+        });
+
+        scene.add(object);
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) mixer.update(delta);
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_loader_fbx_nurbs.ts b/examples-testing/examples/webgl_loader_fbx_nurbs.ts
new file mode 100644
index 000000000..f2e45bcb5
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_fbx_nurbs.ts
@@ -0,0 +1,61 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(2, 18, 28);
+
+    scene = new THREE.Scene();
+
+    // grid
+    const gridHelper = new THREE.GridHelper(28, 28, 0x303030, 0x303030);
+    scene.add(gridHelper);
+
+    // stats
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // model
+    const loader = new FBXLoader();
+    loader.load('models/fbx/nurbs.fbx', function (object) {
+        scene.add(object);
+    });
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 12, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_loader_gcode.ts b/examples-testing/examples/webgl_loader_gcode.ts
new file mode 100644
index 000000000..6fd3e149d
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gcode.ts
@@ -0,0 +1,49 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 70);
+
+    scene = new THREE.Scene();
+
+    const loader = new GCodeLoader();
+    loader.load('models/gcode/benchy.gcode', function (object) {
+        object.position.set(-100, -20, 100);
+        scene.add(object);
+
+        render();
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 10;
+    controls.maxDistance = 100;
+
+    window.addEventListener('resize', resize);
+}
+
+function resize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf.ts b/examples-testing/examples/webgl_loader_gltf.ts
new file mode 100644
index 000000000..e1b0adc51
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf.ts
@@ -0,0 +1,74 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        render();
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', async function (gltf) {
+            const model = gltf.scene;
+
+            // wait until the model can be added to the scene without blocking due to shader compilation
+
+            await renderer.compileAsync(model, camera, scene);
+
+            scene.add(model);
+
+            render();
+        });
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_anisotropy.ts b/examples-testing/examples/webgl_loader_gltf_anisotropy.ts
new file mode 100644
index 000000000..6e240a272
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_anisotropy.ts
@@ -0,0 +1,68 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let renderer, scene, camera, controls;
+
+init();
+
+async function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1.35;
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.set(-0.35, -0.2, 0.35);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, -0.08, 0.11);
+    controls.minDistance = 0.1;
+    controls.maxDistance = 2;
+    controls.addEventListener('change', render);
+    controls.update();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
+
+    const [texture, gltf] = await Promise.all([
+        rgbeLoader.loadAsync('royal_esplanade_1k.hdr'),
+        gltfLoader.loadAsync('AnisotropyBarnLamp.glb'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.backgroundBlurriness = 0.5;
+    scene.environment = texture;
+
+    // model
+
+    scene.add(gltf.scene);
+
+    render();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_avif.ts b/examples-testing/examples/webgl_loader_gltf_avif.ts
new file mode 100644
index 000000000..37d63859e
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_avif.ts
@@ -0,0 +1,61 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(1.5, 4, 9);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf6eedc);
+
+    //
+
+    const dracoLoader = new DRACOLoader();
+    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
+
+    const loader = new GLTFLoader();
+    loader.setDRACOLoader(dracoLoader);
+    loader.setPath('models/gltf/AVIFTest/');
+    loader.load('forest_house.glb', function (gltf) {
+        scene.add(gltf.scene);
+
+        render();
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.target.set(0, 2, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_compressed.ts b/examples-testing/examples/webgl_loader_gltf_compressed.ts
new file mode 100644
index 000000000..3bdcea8ec
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_compressed.ts
@@ -0,0 +1,83 @@
+import * as THREE from 'three';
+
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(0, 100, 0);
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xbbbbbb);
+    scene.environment = pmremGenerator.fromScene(environment).texture;
+    environment.dispose();
+
+    const grid = new THREE.GridHelper(500, 10, 0xffffff, 0xffffff);
+    grid.material.opacity = 0.5;
+    grid.material.depthWrite = false;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
+
+    const loader = new GLTFLoader().setPath('models/gltf/');
+    loader.setKTX2Loader(ktx2Loader);
+    loader.setMeshoptDecoder(MeshoptDecoder);
+    loader.load('coffeemat.glb', function (gltf) {
+        // coffeemat.glb was produced from the source scene using gltfpack:
+        // gltfpack -i coffeemat/scene.gltf -o coffeemat.glb -cc -tc
+        // The resulting model uses EXT_meshopt_compression (for geometry) and KHR_texture_basisu (for texture compression using ETC1S/BasisLZ)
+
+        gltf.scene.position.y = 8;
+
+        scene.add(gltf.scene);
+
+        render();
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 400;
+    controls.maxDistance = 1000;
+    controls.target.set(10, 90, -16);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_dispersion.ts b/examples-testing/examples/webgl_loader_gltf_dispersion.ts
new file mode 100644
index 000000000..100badcab
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_dispersion.ts
@@ -0,0 +1,66 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+let camera, scene, renderer;
+
+init().then(render);
+
+async function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 5);
+    camera.position.set(0.1, 0.05, 0.15);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.NeutralToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene = new THREE.Scene();
+    scene.backgroundBlurriness = 0.5;
+
+    const env = pmremGenerator.fromScene(environment).texture;
+    scene.background = env;
+    scene.environment = env;
+    environment.dispose();
+
+    const loader = new GLTFLoader();
+    const gltf = await loader.loadAsync('models/gltf/DispersionTest.glb');
+
+    scene.add(gltf.scene);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 0.1;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_instancing.ts b/examples-testing/examples/webgl_loader_gltf_instancing.ts
new file mode 100644
index 000000000..5d23e7750
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_instancing.ts
@@ -0,0 +1,69 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-0.9, 0.41, -0.89);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        render();
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF-instancing/');
+        loader.load('DamagedHelmetGpuInstancing.gltf', function (gltf) {
+            scene.add(gltf.scene);
+
+            render();
+        });
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 0.2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0.25, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_iridescence.ts b/examples-testing/examples/webgl_loader_gltf_iridescence.ts
new file mode 100644
index 000000000..eb0f8d914
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_iridescence.ts
@@ -0,0 +1,66 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let renderer, scene, camera, controls;
+
+init().catch(function (err) {
+    console.error(err);
+});
+
+async function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setAnimationLoop(animate);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 20);
+    camera.position.set(0.35, 0.05, 0.35);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -0.5;
+    controls.target.set(0, 0.2, 0);
+    controls.update();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
+
+    const [texture, gltf] = await Promise.all([
+        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
+        gltfLoader.loadAsync('IridescenceLamp.glb'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.environment = texture;
+
+    // model
+
+    scene.add(gltf.scene);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_sheen.ts b/examples-testing/examples/webgl_loader_gltf_sheen.ts
new file mode 100644
index 000000000..bd258d5c1
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_sheen.ts
@@ -0,0 +1,72 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, controls;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
+    camera.position.set(-0.75, 0.7, 1.25);
+
+    scene = new THREE.Scene();
+
+    // model
+
+    new GLTFLoader().setPath('models/gltf/').load('SheenChair.glb', function (gltf) {
+        scene.add(gltf.scene);
+
+        const object = gltf.scene.getObjectByName('SheenChair_fabric');
+
+        const gui = new GUI();
+
+        gui.add(object.material, 'sheen', 0, 1);
+        gui.open();
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene.background = new THREE.Color(0xbbbbbb);
+    scene.environment = pmremGenerator.fromScene(environment).texture;
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0.35, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    controls.update(); // required if damping enabled
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_gltf_transmission.ts b/examples-testing/examples/webgl_loader_gltf_transmission.ts
new file mode 100644
index 000000000..87a47d2c0
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_gltf_transmission.ts
@@ -0,0 +1,80 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+let camera, scene, renderer, controls, clock, mixer;
+
+init();
+
+function init() {
+    clock = new THREE.Clock();
+
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(0, 0.4, 0.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.backgroundBlurriness = 0.35;
+
+        scene.environment = texture;
+
+        // model
+
+        new GLTFLoader()
+            .setPath('models/gltf/')
+            .setDRACOLoader(new DRACOLoader().setDecoderPath('jsm/libs/draco/gltf/'))
+            .load('IridescentDishWithOlives.glb', function (gltf) {
+                mixer = new THREE.AnimationMixer(gltf.scene);
+                mixer.clipAction(gltf.animations[0]).play();
+
+                scene.add(gltf.scene);
+            });
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -0.75;
+    controls.enableDamping = true;
+    controls.minDistance = 0.5;
+    controls.maxDistance = 1;
+    controls.target.set(0, 0.1, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    if (mixer) mixer.update(clock.getDelta());
+
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_imagebitmap.ts b/examples-testing/examples/webgl_loader_imagebitmap.ts
new file mode 100644
index 000000000..1049e9857
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_imagebitmap.ts
@@ -0,0 +1,109 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let group, cubes;
+
+init();
+
+function addImageBitmap() {
+    new THREE.ImageBitmapLoader().load(
+        'textures/planets/earth_atmos_2048.jpg?' + performance.now(),
+        function (imageBitmap) {
+            const texture = new THREE.CanvasTexture(imageBitmap);
+            texture.colorSpace = THREE.SRGBColorSpace;
+            const material = new THREE.MeshBasicMaterial({ map: texture });
+
+            /* ImageBitmap should be disposed when done with it
+						   Can't be done until it's actually uploaded to WebGLTexture */
+
+            // imageBitmap.close();
+
+            addCube(material);
+        },
+        function (p) {
+            console.log(p);
+        },
+        function (e) {
+            console.log(e);
+        },
+    );
+}
+
+function addImage() {
+    new THREE.ImageLoader()
+        .setCrossOrigin('*')
+        .load('textures/planets/earth_atmos_2048.jpg?' + performance.now(), function (image) {
+            const texture = new THREE.CanvasTexture(image);
+            texture.colorSpace = THREE.SRGBColorSpace;
+            const material = new THREE.MeshBasicMaterial({ color: 0xff8888, map: texture });
+            addCube(material);
+        });
+}
+
+const geometry = new THREE.BoxGeometry();
+
+function addCube(material) {
+    const cube = new THREE.Mesh(geometry, material);
+    cube.position.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
+    cube.rotation.set(Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI);
+    cubes.add(cube);
+}
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
+    camera.position.set(0, 4, 7);
+    camera.lookAt(0, 0, 0);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    //
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    group.add(new THREE.GridHelper(4, 12, 0x888888, 0x444444));
+
+    cubes = new THREE.Group();
+    group.add(cubes);
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // TESTS
+
+    setTimeout(addImage, 300);
+    setTimeout(addImage, 600);
+    setTimeout(addImage, 900);
+    setTimeout(addImageBitmap, 1300);
+    setTimeout(addImageBitmap, 1600);
+    setTimeout(addImageBitmap, 1900);
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    group.rotation.y = performance.now() / 3000;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_kmz.ts b/examples-testing/examples/webgl_loader_kmz.ts
new file mode 100644
index 000000000..f93555e41
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_kmz.ts
@@ -0,0 +1,59 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { KMZLoader } from 'three/addons/loaders/KMZLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x999999);
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0.5, 1.0, 0.5).normalize();
+
+    scene.add(light);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
+
+    camera.position.y = 5;
+    camera.position.z = 10;
+
+    scene.add(camera);
+
+    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x7b7b7b);
+    scene.add(grid);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const loader = new KMZLoader();
+    loader.load('./models/kmz/Box.kmz', function (kmz) {
+        kmz.scene.position.y = 0.5;
+        scene.add(kmz.scene);
+        render();
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_lwo.ts b/examples-testing/examples/webgl_loader_lwo.ts
new file mode 100644
index 000000000..fb10c8340
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_lwo.ts
@@ -0,0 +1,69 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LWOLoader } from 'three/addons/loaders/LWOLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 200);
+    camera.position.set(0.7, 14.6, -43.2);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xa0a0a0);
+
+    const ambientLight = new THREE.AmbientLight(0xbbbbbb);
+    scene.add(ambientLight);
+
+    const light1 = new THREE.DirectionalLight(0xc1c1c1, 3);
+    light1.position.set(0, 200, -100);
+    scene.add(light1);
+
+    const grid = new THREE.GridHelper(200, 20, 0x000000, 0x000000);
+    grid.material.opacity = 0.3;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    const loader = new LWOLoader();
+    loader.load('models/lwo/Objects/LWO3/Demo.lwo', function (object) {
+        const phong = object.meshes[0];
+        phong.position.set(2, 12, 0);
+
+        const standard = object.meshes[1];
+        standard.position.set(-2, 12, 0);
+
+        const rocket = object.meshes[2];
+        rocket.position.set(0, 10.5, 1);
+
+        scene.add(phong, standard, rocket);
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(-1.33, 10, 6.7);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_md2_control.ts b/examples-testing/examples/webgl_loader_md2_control.ts
new file mode 100644
index 000000000..683e4c2ad
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_md2_control.ts
@@ -0,0 +1,289 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { MD2CharacterComplex } from 'three/addons/misc/MD2CharacterComplex.js';
+import { Gyroscope } from 'three/addons/misc/Gyroscope.js';
+
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+
+let container, stats;
+let camera, scene, renderer;
+
+const characters = [];
+let nCharacters = 0;
+
+let cameraControls;
+
+const controls = {
+    moveForward: false,
+    moveBackward: false,
+    moveLeft: false,
+    moveRight: false,
+};
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
+    camera.position.set(0, 150, 1300);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+    scene.fog = new THREE.Fog(0xffffff, 1000, 4000);
+
+    scene.add(camera);
+
+    // LIGHTS
+
+    scene.add(new THREE.AmbientLight(0x666666, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 7);
+    light.position.set(200, 450, 500);
+
+    light.castShadow = true;
+
+    light.shadow.mapSize.width = 1024;
+    light.shadow.mapSize.height = 512;
+
+    light.shadow.camera.near = 100;
+    light.shadow.camera.far = 1200;
+
+    light.shadow.camera.left = -1000;
+    light.shadow.camera.right = 1000;
+    light.shadow.camera.top = 350;
+    light.shadow.camera.bottom = -350;
+
+    scene.add(light);
+    // scene.add( new THREE.CameraHelper( light.shadow.camera ) );
+
+    //  GROUND
+
+    const gt = new THREE.TextureLoader().load('textures/terrain/grasslight-big.jpg');
+    const gg = new THREE.PlaneGeometry(16000, 16000);
+    const gm = new THREE.MeshPhongMaterial({ color: 0xffffff, map: gt });
+
+    const ground = new THREE.Mesh(gg, gm);
+    ground.rotation.x = -Math.PI / 2;
+    ground.material.map.repeat.set(64, 64);
+    ground.material.map.wrapS = THREE.RepeatWrapping;
+    ground.material.map.wrapT = THREE.RepeatWrapping;
+    ground.material.map.colorSpace = THREE.SRGBColorSpace;
+    // note that because the ground does not cast a shadow, .castShadow is left false
+    ground.receiveShadow = true;
+
+    scene.add(ground);
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('keydown', onKeyDown);
+    document.addEventListener('keyup', onKeyUp);
+
+    // CONTROLS
+
+    cameraControls = new OrbitControls(camera, renderer.domElement);
+    cameraControls.target.set(0, 50, 0);
+    cameraControls.update();
+
+    // CHARACTER
+
+    const configOgro = {
+        baseUrl: 'models/md2/ogro/',
+
+        body: 'ogro.md2',
+        skins: [
+            'grok.jpg',
+            'ogrobase.png',
+            'arboshak.png',
+            'ctf_r.png',
+            'ctf_b.png',
+            'darkam.png',
+            'freedom.png',
+            'gib.png',
+            'gordogh.png',
+            'igdosh.png',
+            'khorne.png',
+            'nabogro.png',
+            'sharokh.png',
+        ],
+        weapons: [['weapon.md2', 'weapon.jpg']],
+        animations: {
+            move: 'run',
+            idle: 'stand',
+            jump: 'jump',
+            attack: 'attack',
+            crouchMove: 'cwalk',
+            crouchIdle: 'cstand',
+            crouchAttach: 'crattack',
+        },
+
+        walkSpeed: 350,
+        crouchSpeed: 175,
+    };
+
+    const nRows = 1;
+    const nSkins = configOgro.skins.length;
+
+    nCharacters = nSkins * nRows;
+
+    for (let i = 0; i < nCharacters; i++) {
+        const character = new MD2CharacterComplex();
+        character.scale = 3;
+        character.controls = controls;
+        characters.push(character);
+    }
+
+    const baseCharacter = new MD2CharacterComplex();
+    baseCharacter.scale = 3;
+
+    baseCharacter.onLoadComplete = function () {
+        let k = 0;
+
+        for (let j = 0; j < nRows; j++) {
+            for (let i = 0; i < nSkins; i++) {
+                const cloneCharacter = characters[k];
+
+                cloneCharacter.shareParts(baseCharacter);
+
+                // cast and receive shadows
+                cloneCharacter.enableShadows(true);
+
+                cloneCharacter.setWeapon(0);
+                cloneCharacter.setSkin(i);
+
+                cloneCharacter.root.position.x = (i - nSkins / 2) * 150;
+                cloneCharacter.root.position.z = j * 250;
+
+                scene.add(cloneCharacter.root);
+
+                k++;
+            }
+        }
+
+        const gyro = new Gyroscope();
+        gyro.add(camera);
+        gyro.add(light, light.target);
+
+        characters[Math.floor(nSkins / 2)].root.add(gyro);
+    };
+
+    baseCharacter.loadParts(configOgro);
+}
+
+// EVENT HANDLERS
+
+function onWindowResize() {
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+}
+
+function onKeyDown(event) {
+    switch (event.code) {
+        case 'ArrowUp':
+        case 'KeyW':
+            controls.moveForward = true;
+            break;
+
+        case 'ArrowDown':
+        case 'KeyS':
+            controls.moveBackward = true;
+            break;
+
+        case 'ArrowLeft':
+        case 'KeyA':
+            controls.moveLeft = true;
+            break;
+
+        case 'ArrowRight':
+        case 'KeyD':
+            controls.moveRight = true;
+            break;
+
+        // case 'KeyC': controls.crouch = true; break;
+        // case 'Space': controls.jump = true; break;
+        // case 'ControlLeft':
+        // case 'ControlRight': controls.attack = true; break;
+    }
+}
+
+function onKeyUp(event) {
+    switch (event.code) {
+        case 'ArrowUp':
+        case 'KeyW':
+            controls.moveForward = false;
+            break;
+
+        case 'ArrowDown':
+        case 'KeyS':
+            controls.moveBackward = false;
+            break;
+
+        case 'ArrowLeft':
+        case 'KeyA':
+            controls.moveLeft = false;
+            break;
+
+        case 'ArrowRight':
+        case 'KeyD':
+            controls.moveRight = false;
+            break;
+
+        // case 'KeyC': controls.crouch = false; break;
+        // case 'Space': controls.jump = false; break;
+        // case 'ControlLeft':
+        // case 'ControlRight': controls.attack = false; break;
+    }
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    for (let i = 0; i < nCharacters; i++) {
+        characters[i].update(delta);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_mdd.ts b/examples-testing/examples/webgl_loader_mdd.ts
new file mode 100644
index 000000000..5b13e8f4b
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_mdd.ts
@@ -0,0 +1,62 @@
+import * as THREE from 'three';
+
+import { MDDLoader } from 'three/addons/loaders/MDDLoader.js';
+
+let camera, scene, renderer, mixer, clock;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(8, 8, 8);
+    camera.lookAt(scene.position);
+
+    clock = new THREE.Clock();
+
+    //
+
+    const loader = new MDDLoader();
+    loader.load('models/mdd/cube.mdd', function (result) {
+        const morphTargets = result.morphTargets;
+        const clip = result.clip;
+        // clip.optimize(); // optional
+
+        const geometry = new THREE.BoxGeometry();
+        geometry.morphAttributes.position = morphTargets; // apply morph targets
+
+        const material = new THREE.MeshNormalMaterial();
+
+        const mesh = new THREE.Mesh(geometry, material);
+        scene.add(mesh);
+
+        mixer = new THREE.AnimationMixer(mesh);
+        mixer.clipAction(clip).play(); // use clip
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) mixer.update(delta);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_obj.ts b/examples-testing/examples/webgl_loader_obj.ts
new file mode 100644
index 000000000..f61eeb758
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_obj.ts
@@ -0,0 +1,98 @@
+import * as THREE from 'three';
+
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+let object;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
+    camera.position.z = 2.5;
+
+    // scene
+
+    scene = new THREE.Scene();
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 15);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    // manager
+
+    function loadModel() {
+        object.traverse(function (child) {
+            if (child.isMesh) child.material.map = texture;
+        });
+
+        object.position.y = -0.95;
+        object.scale.setScalar(0.01);
+        scene.add(object);
+
+        render();
+    }
+
+    const manager = new THREE.LoadingManager(loadModel);
+
+    // texture
+
+    const textureLoader = new THREE.TextureLoader(manager);
+    const texture = textureLoader.load('textures/uv_grid_opengl.jpg', render);
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    // model
+
+    function onProgress(xhr) {
+        if (xhr.lengthComputable) {
+            const percentComplete = (xhr.loaded / xhr.total) * 100;
+            console.log('model ' + percentComplete.toFixed(2) + '% downloaded');
+        }
+    }
+
+    function onError() {}
+
+    const loader = new OBJLoader(manager);
+    loader.load(
+        'models/obj/male02/male02.obj',
+        function (obj) {
+            object = obj;
+        },
+        onProgress,
+        onError,
+    );
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 5;
+    controls.addEventListener('change', render);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_obj_mtl.ts b/examples-testing/examples/webgl_loader_obj_mtl.ts
new file mode 100644
index 000000000..4308aee7b
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_obj_mtl.ts
@@ -0,0 +1,82 @@
+import * as THREE from 'three';
+
+import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
+    camera.position.z = 2.5;
+
+    // scene
+
+    scene = new THREE.Scene();
+
+    const ambientLight = new THREE.AmbientLight(0xffffff);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 15);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    // model
+
+    const onProgress = function (xhr) {
+        if (xhr.lengthComputable) {
+            const percentComplete = (xhr.loaded / xhr.total) * 100;
+            console.log(percentComplete.toFixed(2) + '% downloaded');
+        }
+    };
+
+    new MTLLoader().setPath('models/obj/male02/').load('male02.mtl', function (materials) {
+        materials.preload();
+
+        new OBJLoader()
+            .setMaterials(materials)
+            .setPath('models/obj/male02/')
+            .load(
+                'male02.obj',
+                function (object) {
+                    object.position.y = -0.95;
+                    object.scale.setScalar(0.01);
+                    scene.add(object);
+                },
+                onProgress,
+            );
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 5;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_pcd.ts b/examples-testing/examples/webgl_loader_pcd.ts
new file mode 100644
index 000000000..d69e3fa2a
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_pcd.ts
@@ -0,0 +1,65 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.01, 40);
+    camera.position.set(0, 0, 1);
+    scene.add(camera);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 0.5;
+    controls.maxDistance = 10;
+
+    //scene.add( new THREE.AxesHelper( 1 ) );
+
+    const loader = new PCDLoader();
+    loader.load('./models/pcd/binary/Zaghetto.pcd', function (points) {
+        points.geometry.center();
+        points.geometry.rotateX(Math.PI);
+        points.name = 'Zaghetto.pcd';
+        scene.add(points);
+
+        //
+
+        const gui = new GUI();
+
+        gui.add(points.material, 'size', 0.001, 0.01).onChange(render);
+        gui.addColor(points.material, 'color').onChange(render);
+        gui.open();
+
+        //
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_pdb.ts b/examples-testing/examples/webgl_loader_pdb.ts
new file mode 100644
index 000000000..b560efa73
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_pdb.ts
@@ -0,0 +1,208 @@
+import * as THREE from 'three';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';
+import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, labelRenderer;
+let controls;
+
+let root;
+
+const MOLECULES = {
+    Ethanol: 'ethanol.pdb',
+    Aspirin: 'aspirin.pdb',
+    Caffeine: 'caffeine.pdb',
+    Nicotine: 'nicotine.pdb',
+    LSD: 'lsd.pdb',
+    Cocaine: 'cocaine.pdb',
+    Cholesterol: 'cholesterol.pdb',
+    Lycopene: 'lycopene.pdb',
+    Glucose: 'glucose.pdb',
+    'Aluminium oxide': 'Al2O3.pdb',
+    Cubane: 'cubane.pdb',
+    Copper: 'cu.pdb',
+    Fluorite: 'caf2.pdb',
+    Salt: 'nacl.pdb',
+    'YBCO superconductor': 'ybco.pdb',
+    Buckyball: 'buckyball.pdb',
+    Graphite: 'graphite.pdb',
+};
+
+const params = {
+    molecule: 'caffeine.pdb',
+};
+
+const loader = new PDBLoader();
+const offset = new THREE.Vector3();
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.z = 1000;
+    scene.add(camera);
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 2.5);
+    light1.position.set(1, 1, 1);
+    scene.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 1.5);
+    light2.position.set(-1, -1, 1);
+    scene.add(light2);
+
+    root = new THREE.Group();
+    scene.add(root);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.getElementById('container').appendChild(renderer.domElement);
+
+    labelRenderer = new CSS2DRenderer();
+    labelRenderer.setSize(window.innerWidth, window.innerHeight);
+    labelRenderer.domElement.style.position = 'absolute';
+    labelRenderer.domElement.style.top = '0px';
+    labelRenderer.domElement.style.pointerEvents = 'none';
+    document.getElementById('container').appendChild(labelRenderer.domElement);
+
+    //
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.minDistance = 500;
+    controls.maxDistance = 2000;
+
+    //
+
+    loadMolecule(params.molecule);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'molecule', MOLECULES).onChange(loadMolecule);
+    gui.open();
+}
+
+//
+
+function loadMolecule(model) {
+    const url = 'models/pdb/' + model;
+
+    while (root.children.length > 0) {
+        const object = root.children[0];
+        object.parent.remove(object);
+    }
+
+    loader.load(url, function (pdb) {
+        const geometryAtoms = pdb.geometryAtoms;
+        const geometryBonds = pdb.geometryBonds;
+        const json = pdb.json;
+
+        const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
+        const sphereGeometry = new THREE.IcosahedronGeometry(1, 3);
+
+        geometryAtoms.computeBoundingBox();
+        geometryAtoms.boundingBox.getCenter(offset).negate();
+
+        geometryAtoms.translate(offset.x, offset.y, offset.z);
+        geometryBonds.translate(offset.x, offset.y, offset.z);
+
+        let positions = geometryAtoms.getAttribute('position');
+        const colors = geometryAtoms.getAttribute('color');
+
+        const position = new THREE.Vector3();
+        const color = new THREE.Color();
+
+        for (let i = 0; i < positions.count; i++) {
+            position.x = positions.getX(i);
+            position.y = positions.getY(i);
+            position.z = positions.getZ(i);
+
+            color.r = colors.getX(i);
+            color.g = colors.getY(i);
+            color.b = colors.getZ(i);
+
+            const material = new THREE.MeshPhongMaterial({ color: color });
+
+            const object = new THREE.Mesh(sphereGeometry, material);
+            object.position.copy(position);
+            object.position.multiplyScalar(75);
+            object.scale.multiplyScalar(25);
+            root.add(object);
+
+            const atom = json.atoms[i];
+
+            const text = document.createElement('div');
+            text.className = 'label';
+            text.style.color = 'rgb(' + atom[3][0] + ',' + atom[3][1] + ',' + atom[3][2] + ')';
+            text.textContent = atom[4];
+
+            const label = new CSS2DObject(text);
+            label.position.copy(object.position);
+            root.add(label);
+        }
+
+        positions = geometryBonds.getAttribute('position');
+
+        const start = new THREE.Vector3();
+        const end = new THREE.Vector3();
+
+        for (let i = 0; i < positions.count; i += 2) {
+            start.x = positions.getX(i);
+            start.y = positions.getY(i);
+            start.z = positions.getZ(i);
+
+            end.x = positions.getX(i + 1);
+            end.y = positions.getY(i + 1);
+            end.z = positions.getZ(i + 1);
+
+            start.multiplyScalar(75);
+            end.multiplyScalar(75);
+
+            const object = new THREE.Mesh(boxGeometry, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+            object.position.copy(start);
+            object.position.lerp(end, 0.5);
+            object.scale.set(5, 5, start.distanceTo(end));
+            object.lookAt(end);
+            root.add(object);
+        }
+    });
+}
+
+//
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    labelRenderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+
+    const time = Date.now() * 0.0004;
+
+    root.rotation.x = time;
+    root.rotation.y = time * 0.7;
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+    labelRenderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_ply.ts b/examples-testing/examples/webgl_loader_ply.ts
new file mode 100644
index 000000000..0f4042b7d
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_ply.ts
@@ -0,0 +1,146 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
+
+let container, stats;
+
+let camera, cameraTarget, scene, renderer;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15);
+    camera.position.set(3, 0.15, 3);
+
+    cameraTarget = new THREE.Vector3(0, -0.1, 0);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x72645b);
+    scene.fog = new THREE.Fog(0x72645b, 2, 15);
+
+    // Ground
+
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(40, 40),
+        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, specular: 0x474747 }),
+    );
+    plane.rotation.x = -Math.PI / 2;
+    plane.position.y = -0.5;
+    scene.add(plane);
+
+    plane.receiveShadow = true;
+
+    // PLY file
+
+    const loader = new PLYLoader();
+    loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshStandardMaterial({ color: 0x009cff, flatShading: true });
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.y = -0.2;
+        mesh.position.z = 0.3;
+        mesh.rotation.x = -Math.PI / 2;
+        mesh.scale.multiplyScalar(0.001);
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+    });
+
+    loader.load('./models/ply/binary/Lucy100k.ply', function (geometry) {
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshStandardMaterial({ color: 0x009cff, flatShading: true });
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = -0.2;
+        mesh.position.y = -0.02;
+        mesh.position.z = -0.2;
+        mesh.scale.multiplyScalar(0.0006);
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+    });
+
+    // Lights
+
+    scene.add(new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3));
+
+    addShadowedLight(1, 1, 1, 0xffffff, 3.5);
+    addShadowedLight(0.5, 1, -1, 0xffd500, 3);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    renderer.shadowMap.enabled = true;
+
+    container.appendChild(renderer.domElement);
+
+    // stats
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // resize
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function addShadowedLight(x, y, z, color, intensity) {
+    const directionalLight = new THREE.DirectionalLight(color, intensity);
+    directionalLight.position.set(x, y, z);
+    scene.add(directionalLight);
+
+    directionalLight.castShadow = true;
+
+    const d = 1;
+    directionalLight.shadow.camera.left = -d;
+    directionalLight.shadow.camera.right = d;
+    directionalLight.shadow.camera.top = d;
+    directionalLight.shadow.camera.bottom = -d;
+
+    directionalLight.shadow.camera.near = 1;
+    directionalLight.shadow.camera.far = 4;
+
+    directionalLight.shadow.mapSize.width = 1024;
+    directionalLight.shadow.mapSize.height = 1024;
+
+    directionalLight.shadow.bias = -0.001;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const timer = Date.now() * 0.0005;
+
+    camera.position.x = Math.sin(timer) * 2.5;
+    camera.position.z = Math.cos(timer) * 2.5;
+
+    camera.lookAt(cameraTarget);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_svg.ts b/examples-testing/examples/webgl_loader_svg.ts
new file mode 100644
index 000000000..45361b92f
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_svg.ts
@@ -0,0 +1,193 @@
+import * as THREE from 'three';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
+
+let renderer, scene, camera, gui, guiData;
+
+init();
+
+//
+
+function init() {
+    const container = document.getElementById('container');
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 200);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.screenSpacePanning = true;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    guiData = {
+        currentURL: 'models/svg/tiger.svg',
+        drawFillShapes: true,
+        drawStrokes: true,
+        fillShapesWireframe: false,
+        strokesWireframe: false,
+    };
+
+    loadSVG(guiData.currentURL);
+
+    createGUI();
+}
+
+function createGUI() {
+    if (gui) gui.destroy();
+
+    gui = new GUI();
+
+    gui.add(guiData, 'currentURL', {
+        Tiger: 'models/svg/tiger.svg',
+        'Joins and caps': 'models/svg/lineJoinsAndCaps.svg',
+        Hexagon: 'models/svg/hexagon.svg',
+        Energy: 'models/svg/energy.svg',
+        'Test 1': 'models/svg/tests/1.svg',
+        'Test 2': 'models/svg/tests/2.svg',
+        'Test 3': 'models/svg/tests/3.svg',
+        'Test 4': 'models/svg/tests/4.svg',
+        'Test 5': 'models/svg/tests/5.svg',
+        'Test 6': 'models/svg/tests/6.svg',
+        'Test 7': 'models/svg/tests/7.svg',
+        'Test 8': 'models/svg/tests/8.svg',
+        'Test 9': 'models/svg/tests/9.svg',
+        Units: 'models/svg/tests/units.svg',
+        Ordering: 'models/svg/tests/ordering.svg',
+        Defs: 'models/svg/tests/testDefs/Svg-defs.svg',
+        Defs2: 'models/svg/tests/testDefs/Svg-defs2.svg',
+        Defs3: 'models/svg/tests/testDefs/Wave-defs.svg',
+        Defs4: 'models/svg/tests/testDefs/defs4.svg',
+        Defs5: 'models/svg/tests/testDefs/defs5.svg',
+        'Style CSS inside defs': 'models/svg/style-css-inside-defs.svg',
+        'Multiple CSS classes': 'models/svg/multiple-css-classes.svg',
+        'Zero Radius': 'models/svg/zero-radius.svg',
+        'Styles in svg tag': 'models/svg/tests/styles.svg',
+        'Round join': 'models/svg/tests/roundJoinPrecisionIssue.svg',
+        'Ellipse Transformations': 'models/svg/tests/ellipseTransform.svg',
+        singlePointTest: 'models/svg/singlePointTest.svg',
+        singlePointTest2: 'models/svg/singlePointTest2.svg',
+        singlePointTest3: 'models/svg/singlePointTest3.svg',
+        emptyPath: 'models/svg/emptyPath.svg',
+    })
+        .name('SVG File')
+        .onChange(update);
+
+    gui.add(guiData, 'drawStrokes').name('Draw strokes').onChange(update);
+
+    gui.add(guiData, 'drawFillShapes').name('Draw fill shapes').onChange(update);
+
+    gui.add(guiData, 'strokesWireframe').name('Wireframe strokes').onChange(update);
+
+    gui.add(guiData, 'fillShapesWireframe').name('Wireframe fill shapes').onChange(update);
+
+    function update() {
+        loadSVG(guiData.currentURL);
+    }
+}
+
+function loadSVG(url) {
+    //
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xb0b0b0);
+
+    //
+
+    const helper = new THREE.GridHelper(160, 10, 0x8d8d8d, 0xc1c1c1);
+    helper.rotation.x = Math.PI / 2;
+    scene.add(helper);
+
+    //
+
+    const loader = new SVGLoader();
+
+    loader.load(url, function (data) {
+        const group = new THREE.Group();
+        group.scale.multiplyScalar(0.25);
+        group.position.x = -70;
+        group.position.y = 70;
+        group.scale.y *= -1;
+
+        let renderOrder = 0;
+
+        for (const path of data.paths) {
+            const fillColor = path.userData.style.fill;
+
+            if (guiData.drawFillShapes && fillColor !== undefined && fillColor !== 'none') {
+                const material = new THREE.MeshBasicMaterial({
+                    color: new THREE.Color().setStyle(fillColor),
+                    opacity: path.userData.style.fillOpacity,
+                    transparent: true,
+                    side: THREE.DoubleSide,
+                    depthWrite: false,
+                    wireframe: guiData.fillShapesWireframe,
+                });
+
+                const shapes = SVGLoader.createShapes(path);
+
+                for (const shape of shapes) {
+                    const geometry = new THREE.ShapeGeometry(shape);
+                    const mesh = new THREE.Mesh(geometry, material);
+                    mesh.renderOrder = renderOrder++;
+
+                    group.add(mesh);
+                }
+            }
+
+            const strokeColor = path.userData.style.stroke;
+
+            if (guiData.drawStrokes && strokeColor !== undefined && strokeColor !== 'none') {
+                const material = new THREE.MeshBasicMaterial({
+                    color: new THREE.Color().setStyle(strokeColor),
+                    opacity: path.userData.style.strokeOpacity,
+                    transparent: true,
+                    side: THREE.DoubleSide,
+                    depthWrite: false,
+                    wireframe: guiData.strokesWireframe,
+                });
+
+                for (const subPath of path.subPaths) {
+                    const geometry = SVGLoader.pointsToStroke(subPath.getPoints(), path.userData.style);
+
+                    if (geometry) {
+                        const mesh = new THREE.Mesh(geometry, material);
+                        mesh.renderOrder = renderOrder++;
+
+                        group.add(mesh);
+                    }
+                }
+            }
+        }
+
+        scene.add(group);
+
+        render();
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_texture_dds.ts b/examples-testing/examples/webgl_loader_texture_dds.ts
new file mode 100644
index 000000000..bc4bd0572
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_dds.ts
@@ -0,0 +1,207 @@
+import * as THREE from 'three';
+
+import { DDSLoader } from 'three/addons/loaders/DDSLoader.js';
+
+let camera, scene, renderer;
+const meshes = [];
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 15;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.BoxGeometry(2, 2, 2);
+
+    /*
+				This is how compressed textures are supposed to be used:
+
+				DXT1 - RGB - opaque textures
+				DXT3 - RGBA - transparent textures with sharp alpha transitions
+				DXT5 - RGBA - transparent textures with full alpha range
+				*/
+
+    const loader = new DDSLoader();
+
+    const map1 = loader.load('textures/compressed/disturb_dxt1_nomip.dds');
+    map1.minFilter = map1.magFilter = THREE.LinearFilter;
+    map1.anisotropy = 4;
+    map1.colorSpace = THREE.SRGBColorSpace;
+
+    const map2 = loader.load('textures/compressed/disturb_dxt1_mip.dds');
+    map2.anisotropy = 4;
+    map2.colorSpace = THREE.SRGBColorSpace;
+
+    const map3 = loader.load('textures/compressed/hepatica_dxt3_mip.dds');
+    map3.anisotropy = 4;
+    map3.colorSpace = THREE.SRGBColorSpace;
+
+    const map4 = loader.load('textures/compressed/explosion_dxt5_mip.dds');
+    map4.anisotropy = 4;
+    map4.colorSpace = THREE.SRGBColorSpace;
+
+    const map5 = loader.load('textures/compressed/disturb_argb_nomip.dds');
+    map5.minFilter = map5.magFilter = THREE.LinearFilter;
+    map5.anisotropy = 4;
+    map5.colorSpace = THREE.SRGBColorSpace;
+
+    const map6 = loader.load('textures/compressed/disturb_argb_mip.dds');
+    map6.anisotropy = 4;
+    map6.colorSpace = THREE.SRGBColorSpace;
+
+    const map7 = loader.load('textures/compressed/disturb_dx10_bc6h_signed_nomip.dds');
+    map7.anisotropy = 4;
+
+    const map8 = loader.load('textures/compressed/disturb_dx10_bc6h_signed_mip.dds');
+    map8.anisotropy = 4;
+
+    const map9 = loader.load('textures/compressed/disturb_dx10_bc6h_unsigned_nomip.dds');
+    map9.anisotropy = 4;
+
+    const map10 = loader.load('textures/compressed/disturb_dx10_bc6h_unsigned_mip.dds');
+    map10.anisotropy = 4;
+
+    const cubemap1 = loader.load('textures/compressed/Mountains.dds', function (texture) {
+        texture.magFilter = THREE.LinearFilter;
+        texture.minFilter = THREE.LinearFilter;
+        texture.mapping = THREE.CubeReflectionMapping;
+        texture.colorSpace = THREE.SRGBColorSpace;
+        material1.needsUpdate = true;
+    });
+
+    const cubemap2 = loader.load('textures/compressed/Mountains_argb_mip.dds', function (texture) {
+        texture.magFilter = THREE.LinearFilter;
+        texture.minFilter = THREE.LinearFilter;
+        texture.mapping = THREE.CubeReflectionMapping;
+        texture.colorSpace = THREE.SRGBColorSpace;
+        material5.needsUpdate = true;
+    });
+
+    const cubemap3 = loader.load('textures/compressed/Mountains_argb_nomip.dds', function (texture) {
+        texture.magFilter = THREE.LinearFilter;
+        texture.minFilter = THREE.LinearFilter;
+        texture.mapping = THREE.CubeReflectionMapping;
+        texture.colorSpace = THREE.SRGBColorSpace;
+        material6.needsUpdate = true;
+    });
+
+    const material1 = new THREE.MeshBasicMaterial({ map: map1, envMap: cubemap1 });
+    const material2 = new THREE.MeshBasicMaterial({ map: map2 });
+    const material3 = new THREE.MeshBasicMaterial({ map: map3, alphaTest: 0.5, side: THREE.DoubleSide });
+    const material4 = new THREE.MeshBasicMaterial({
+        map: map4,
+        side: THREE.DoubleSide,
+        blending: THREE.AdditiveBlending,
+        depthTest: false,
+        transparent: true,
+    });
+    const material5 = new THREE.MeshBasicMaterial({ envMap: cubemap2 });
+    const material6 = new THREE.MeshBasicMaterial({ envMap: cubemap3 });
+    const material7 = new THREE.MeshBasicMaterial({ map: map5 });
+    const material8 = new THREE.MeshBasicMaterial({ map: map6 });
+    const material9 = new THREE.MeshBasicMaterial({ map: map7 });
+    const material10 = new THREE.MeshBasicMaterial({ map: map8 });
+    const material11 = new THREE.MeshBasicMaterial({ map: map9 });
+    const material12 = new THREE.MeshBasicMaterial({ map: map10 });
+
+    let mesh = new THREE.Mesh(new THREE.TorusGeometry(), material1);
+    mesh.position.x = -10;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material2);
+    mesh.position.x = -6;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material3);
+    mesh.position.x = -6;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material4);
+    mesh.position.x = -10;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material5);
+    mesh.position.x = -2;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material6);
+    mesh.position.x = -2;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material7);
+    mesh.position.x = 2;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material8);
+    mesh.position.x = 2;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material9);
+    mesh.position.x = 6;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material10);
+    mesh.position.x = 6;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material11);
+    mesh.position.x = 10;
+    mesh.position.y = -2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    mesh = new THREE.Mesh(geometry, material12);
+    mesh.position.x = 10;
+    mesh.position.y = 2;
+    scene.add(mesh);
+    meshes.push(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    for (let i = 0; i < meshes.length; i++) {
+        const mesh = meshes[i];
+        mesh.rotation.x = time;
+        mesh.rotation.y = time;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_texture_ktx.ts b/examples-testing/examples/webgl_loader_texture_ktx.ts
new file mode 100644
index 000000000..af66eb810
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_ktx.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import { KTXLoader } from 'three/addons/loaders/KTXLoader.js';
+
+/*
+	This is how compressed textures are supposed to be used:
+
+	best for desktop:
+	BC1(DXT1) - opaque textures
+	BC3(DXT5) - transparent textures with full alpha range
+
+	best for iOS:
+	PVR2, PVR4 - opaque textures or alpha
+
+	best for Android:
+	ETC1 - opaque textures
+	ASTC_4x4, ASTC8x8 - transparent textures with full alpha range
+	*/
+
+let camera, scene, renderer;
+const meshes = [];
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    const formats = {
+        astc: renderer.extensions.has('WEBGL_compressed_texture_astc'),
+        etc1: renderer.extensions.has('WEBGL_compressed_texture_etc1'),
+        s3tc: renderer.extensions.has('WEBGL_compressed_texture_s3tc'),
+        pvrtc: renderer.extensions.has('WEBGL_compressed_texture_pvrtc'),
+    };
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.BoxGeometry(200, 200, 200);
+    let material1, material2;
+
+    // TODO: add cubemap support
+    const loader = new KTXLoader();
+
+    if (formats.pvrtc) {
+        material1 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/disturb_PVR2bpp.ktx'),
+        });
+        material1.map.colorSpace = THREE.SRGBColorSpace;
+        material2 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/lensflare_PVR4bpp.ktx'),
+            depthTest: false,
+            transparent: true,
+            side: THREE.DoubleSide,
+        });
+        material2.map.colorSpace = THREE.SRGBColorSpace;
+
+        meshes.push(new THREE.Mesh(geometry, material1));
+        meshes.push(new THREE.Mesh(geometry, material2));
+    }
+
+    if (formats.s3tc) {
+        material1 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/disturb_BC1.ktx'),
+        });
+        material1.map.colorSpace = THREE.SRGBColorSpace;
+        material2 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/lensflare_BC3.ktx'),
+            depthTest: false,
+            transparent: true,
+            side: THREE.DoubleSide,
+        });
+        material2.map.colorSpace = THREE.SRGBColorSpace;
+
+        meshes.push(new THREE.Mesh(geometry, material1));
+        meshes.push(new THREE.Mesh(geometry, material2));
+    }
+
+    if (formats.etc1) {
+        material1 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/disturb_ETC1.ktx'),
+        });
+
+        meshes.push(new THREE.Mesh(geometry, material1));
+    }
+
+    if (formats.astc) {
+        material1 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/disturb_ASTC4x4.ktx'),
+        });
+        material1.map.colorSpace = THREE.SRGBColorSpace;
+        material2 = new THREE.MeshBasicMaterial({
+            map: loader.load('textures/compressed/lensflare_ASTC8x8.ktx'),
+            depthTest: false,
+            transparent: true,
+            side: THREE.DoubleSide,
+        });
+        material2.map.colorSpace = THREE.SRGBColorSpace;
+
+        meshes.push(new THREE.Mesh(geometry, material1));
+        meshes.push(new THREE.Mesh(geometry, material2));
+    }
+
+    let x = (-meshes.length / 2) * 150;
+    for (let i = 0; i < meshes.length; ++i, x += 300) {
+        const mesh = meshes[i];
+        mesh.position.x = x;
+        mesh.position.y = 0;
+        scene.add(mesh);
+    }
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = Date.now() * 0.001;
+
+    for (let i = 0; i < meshes.length; i++) {
+        const mesh = meshes[i];
+        mesh.rotation.x = time;
+        mesh.rotation.y = time;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_texture_rgbm.ts b/examples-testing/examples/webgl_loader_texture_rgbm.ts
new file mode 100644
index 000000000..a882cdbc5
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_rgbm.ts
@@ -0,0 +1,75 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
+
+const params = {
+    exposure: 2.0,
+};
+
+let renderer, scene, camera;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    renderer.toneMappingExposure = params.exposure;
+
+    scene = new THREE.Scene();
+
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0, 1);
+
+    new RGBMLoader().setMaxRange(16).load('textures/memorial.png', function (texture) {
+        const material = new THREE.MeshBasicMaterial({ map: texture });
+
+        const quad = new THREE.PlaneGeometry(1, 1.5);
+
+        const mesh = new THREE.Mesh(quad, material);
+
+        scene.add(mesh);
+
+        render();
+    });
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'exposure', 0, 4, 0.01).onChange(render);
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    const frustumHeight = camera.top - camera.bottom;
+
+    camera.left = (-frustumHeight * aspect) / 2;
+    camera.right = (frustumHeight * aspect) / 2;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.toneMappingExposure = params.exposure;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_texture_tga.ts b/examples-testing/examples/webgl_loader_texture_tga.ts
new file mode 100644
index 000000000..c4f65b79a
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_tga.ts
@@ -0,0 +1,90 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { TGALoader } from 'three/addons/loaders/TGALoader.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 1, 5);
+
+    scene = new THREE.Scene();
+
+    //
+
+    const loader = new TGALoader();
+    const geometry = new THREE.BoxGeometry();
+
+    // add box 1 - grey8 texture
+
+    const texture1 = loader.load('textures/crate_grey8.tga');
+    texture1.colorSpace = THREE.SRGBColorSpace;
+    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
+
+    const mesh1 = new THREE.Mesh(geometry, material1);
+    mesh1.position.x = -1;
+
+    scene.add(mesh1);
+
+    // add box 2 - tga texture
+
+    const texture2 = loader.load('textures/crate_color8.tga');
+    texture2.colorSpace = THREE.SRGBColorSpace;
+    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
+
+    const mesh2 = new THREE.Mesh(geometry, material2);
+    mesh2.position.x = 1;
+
+    scene.add(mesh2);
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
+    scene.add(ambientLight);
+
+    const light = new THREE.DirectionalLight(0xffffff, 2.5);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_loader_texture_tiff.ts b/examples-testing/examples/webgl_loader_texture_tiff.ts
new file mode 100644
index 000000000..f097774aa
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_tiff.ts
@@ -0,0 +1,87 @@
+import * as THREE from 'three';
+
+import { TIFFLoader } from 'three/addons/loaders/TIFFLoader.js';
+
+let renderer, scene, camera;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.set(0, 0, 4);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    const loader = new TIFFLoader();
+
+    const geometry = new THREE.PlaneGeometry();
+
+    // uncompressed
+
+    loader.load('textures/tiff/crate_uncompressed.tif', function (texture) {
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        const material = new THREE.MeshBasicMaterial({ map: texture });
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(-1.5, 0, 0);
+
+        scene.add(mesh);
+
+        render();
+    });
+
+    // LZW
+
+    loader.load('textures/tiff/crate_lzw.tif', function (texture) {
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        const material = new THREE.MeshBasicMaterial({ map: texture });
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(0, 0, 0);
+
+        scene.add(mesh);
+
+        render();
+    });
+
+    // JPEG
+
+    loader.load('textures/tiff/crate_jpeg.tif', function (texture) {
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        const material = new THREE.MeshBasicMaterial({ map: texture });
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(1.5, 0, 0);
+
+        scene.add(mesh);
+
+        render();
+    });
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_texture_ultrahdr.ts b/examples-testing/examples/webgl_loader_texture_ultrahdr.ts
new file mode 100644
index 000000000..c8bce4bf9
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_texture_ultrahdr.ts
@@ -0,0 +1,101 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+const params = {
+    autoRotate: true,
+    metalness: 1.0,
+    roughness: 0.0,
+    exposure: 1.0,
+    resolution: '2k',
+    type: 'HalfFloatType',
+};
+
+let renderer, scene, camera, controls, torusMesh, loader;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = params.exposure;
+
+    renderer.setAnimationLoop(render);
+
+    scene = new THREE.Scene();
+
+    torusMesh = new THREE.Mesh(
+        new THREE.TorusKnotGeometry(1, 0.4, 128, 128, 1, 3),
+        new THREE.MeshStandardMaterial({ roughness: params.roughness, metalness: params.metalness }),
+    );
+    scene.add(torusMesh);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.set(0.0, 0.0, -6.0);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+
+    loader = new UltraHDRLoader();
+    loader.setDataType(THREE.FloatType);
+
+    const loadEnvironment = function (resolution = '2k', type = 'HalfFloatType') {
+        loader.setDataType(THREE[type]);
+
+        loader.load(`textures/equirectangular/spruit_sunrise_${resolution}.hdr.jpg`, function (texture) {
+            texture.mapping = THREE.EquirectangularReflectionMapping;
+            texture.needsUpdate = true;
+
+            scene.background = texture;
+            scene.environment = texture;
+        });
+    };
+
+    loadEnvironment(params.resolution, params.type);
+
+    const gui = new GUI();
+
+    gui.add(params, 'autoRotate');
+    gui.add(params, 'metalness', 0, 1, 0.01);
+    gui.add(params, 'roughness', 0, 1, 0.01);
+    gui.add(params, 'exposure', 0, 4, 0.01);
+    gui.add(params, 'resolution', ['2k', '4k']).onChange(value => {
+        loadEnvironment(value, params.type);
+    });
+    gui.add(params, 'type', ['HalfFloatType', 'FloatType']).onChange(value => {
+        loadEnvironment(params.resolution, value);
+    });
+
+    gui.open();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render() {
+    torusMesh.material.roughness = params.roughness;
+    torusMesh.material.metalness = params.metalness;
+
+    if (params.autoRotate) {
+        torusMesh.rotation.y += 0.005;
+    }
+
+    renderer.toneMappingExposure = params.exposure;
+
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_tilt.ts b/examples-testing/examples/webgl_loader_tilt.ts
new file mode 100644
index 000000000..2a583c2b0
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_tilt.ts
@@ -0,0 +1,54 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { TiltLoader } from 'three/addons/loaders/TiltLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
+
+    camera.position.y = 43;
+    camera.position.z = 100;
+
+    scene.add(camera);
+
+    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x555555);
+    scene.add(grid);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    const loader = new TiltLoader();
+    loader.load('./models/tilt/BRUSH_DOME.tilt', function (object) {
+        // console.log( object.children.length );
+        scene.add(object);
+        render();
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.target.y = camera.position.y;
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_ttf.ts b/examples-testing/examples/webgl_loader_ttf.ts
new file mode 100644
index 000000000..168371a14
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_ttf.ts
@@ -0,0 +1,231 @@
+import * as THREE from 'three';
+
+import { TTFLoader } from 'three/addons/loaders/TTFLoader.js';
+import { Font } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+let container;
+let camera, cameraTarget, scene, renderer;
+let group, textMesh1, textMesh2, textGeo, material;
+let firstLetter = true;
+
+let text = 'three.js';
+const depth = 20,
+    size = 70,
+    hover = 30,
+    curveSegments = 4,
+    bevelThickness = 2,
+    bevelSize = 1.5;
+
+let font = null;
+const mirror = true;
+
+let targetRotation = 0;
+let targetRotationOnPointerDown = 0;
+
+let pointerX = 0;
+let pointerXOnPointerDown = 0;
+
+let windowHalfX = window.innerWidth / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
+    camera.position.set(0, 400, 700);
+
+    cameraTarget = new THREE.Vector3(0, 150, 0);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x000000);
+    scene.fog = new THREE.Fog(0x000000, 250, 1400);
+
+    // LIGHTS
+
+    const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.4);
+    dirLight1.position.set(0, 0, 1).normalize();
+    scene.add(dirLight1);
+
+    const dirLight2 = new THREE.DirectionalLight(0xffffff, 2);
+    dirLight2.position.set(0, hover, 10).normalize();
+    dirLight2.color.setHSL(Math.random(), 1, 0.5, THREE.SRGBColorSpace);
+    scene.add(dirLight2);
+
+    material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
+
+    group = new THREE.Group();
+    group.position.y = 100;
+
+    scene.add(group);
+
+    const loader = new TTFLoader();
+
+    loader.load('fonts/ttf/kenpixel.ttf', function (json) {
+        font = new Font(json);
+        createText();
+    });
+
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(10000, 10000),
+        new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }),
+    );
+    plane.position.y = 100;
+    plane.rotation.x = -Math.PI / 2;
+    scene.add(plane);
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // EVENTS
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointerdown', onPointerDown);
+
+    document.addEventListener('keypress', onDocumentKeyPress);
+    document.addEventListener('keydown', onDocumentKeyDown);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentKeyDown(event) {
+    if (firstLetter) {
+        firstLetter = false;
+        text = '';
+    }
+
+    const keyCode = event.keyCode;
+
+    // backspace
+
+    if (keyCode === 8) {
+        event.preventDefault();
+
+        text = text.substring(0, text.length - 1);
+        refreshText();
+
+        return false;
+    }
+}
+
+function onDocumentKeyPress(event) {
+    const keyCode = event.which;
+
+    // backspace
+
+    if (keyCode === 8) {
+        event.preventDefault();
+    } else {
+        const ch = String.fromCharCode(keyCode);
+        text += ch;
+
+        refreshText();
+    }
+}
+
+function createText() {
+    textGeo = new TextGeometry(text, {
+        font: font,
+
+        size: size,
+        depth: depth,
+        curveSegments: curveSegments,
+
+        bevelThickness: bevelThickness,
+        bevelSize: bevelSize,
+        bevelEnabled: true,
+    });
+
+    textGeo.computeBoundingBox();
+    textGeo.computeVertexNormals();
+
+    const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
+
+    textMesh1 = new THREE.Mesh(textGeo, material);
+
+    textMesh1.position.x = centerOffset;
+    textMesh1.position.y = hover;
+    textMesh1.position.z = 0;
+
+    textMesh1.rotation.x = 0;
+    textMesh1.rotation.y = Math.PI * 2;
+
+    group.add(textMesh1);
+
+    if (mirror) {
+        textMesh2 = new THREE.Mesh(textGeo, material);
+
+        textMesh2.position.x = centerOffset;
+        textMesh2.position.y = -hover;
+        textMesh2.position.z = depth;
+
+        textMesh2.rotation.x = Math.PI;
+        textMesh2.rotation.y = Math.PI * 2;
+
+        group.add(textMesh2);
+    }
+}
+
+function refreshText() {
+    group.remove(textMesh1);
+    if (mirror) group.remove(textMesh2);
+
+    if (!text) return;
+
+    createText();
+}
+
+function onPointerDown(event) {
+    if (event.isPrimary === false) return;
+
+    pointerXOnPointerDown = event.clientX - windowHalfX;
+    targetRotationOnPointerDown = targetRotation;
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    pointerX = event.clientX - windowHalfX;
+
+    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
+}
+
+function onPointerUp() {
+    if (event.isPrimary === false) return;
+
+    document.removeEventListener('pointermove', onPointerMove);
+    document.removeEventListener('pointerup', onPointerUp);
+}
+
+//
+
+function animate() {
+    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
+
+    camera.lookAt(cameraTarget);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_usdz.ts b/examples-testing/examples/webgl_loader_usdz.ts
new file mode 100644
index 000000000..d75823d88
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_usdz.ts
@@ -0,0 +1,68 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { USDZLoader } from 'three/addons/loaders/USDZLoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0.75, -1.5);
+
+    scene = new THREE.Scene();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+
+    const usdzLoader = new USDZLoader().setPath('models/usdz/');
+
+    const [texture, model] = await Promise.all([
+        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
+        usdzLoader.loadAsync('saeukkang.usdz'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.backgroundBlurriness = 0.5;
+    scene.environment = texture;
+
+    // model
+
+    model.position.y = 0.25;
+    model.position.z = -0.25;
+    scene.add(model);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 2.0;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 8;
+    // controls.target.y = 15;
+    // controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_vox.ts b/examples-testing/examples/webgl_loader_vox.ts
new file mode 100644
index 000000000..061848012
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_vox.ts
@@ -0,0 +1,104 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { VOXLoader, VOXMesh } from 'three/addons/loaders/VOXLoader.js';
+
+let camera, controls, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.set(0.175, 0.075, 0.175);
+
+    scene = new THREE.Scene();
+    scene.add(camera);
+
+    // light
+
+    const hemiLight = new THREE.HemisphereLight(0xcccccc, 0x444444, 3);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 2.5);
+    dirLight.position.set(1.5, 3, 2.5);
+    scene.add(dirLight);
+
+    const dirLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
+    dirLight2.position.set(-1.5, -3, -2.5);
+    scene.add(dirLight2);
+
+    const loader = new VOXLoader();
+    loader.load('models/vox/monu10.vox', function (chunks) {
+        for (let i = 0; i < chunks.length; i++) {
+            const chunk = chunks[i];
+
+            // displayPalette( chunk.palette );
+
+            const mesh = new VOXMesh(chunk);
+            mesh.scale.setScalar(0.0015);
+            scene.add(mesh);
+        }
+    });
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 0.1;
+    controls.maxDistance = 0.5;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+/*
+			function displayPalette( palette ) {
+
+				const canvas = document.createElement( 'canvas' );
+				canvas.width = 8;
+				canvas.height = 32;
+				canvas.style.position = 'absolute';
+				canvas.style.top = '0';
+				canvas.style.width = '100px';
+				canvas.style.imageRendering = 'pixelated';
+				document.body.appendChild( canvas );
+
+				const context = canvas.getContext( '2d' );
+
+				for ( let c = 0; c < 256; c ++ ) {
+
+					const x = c % 8;
+					const y = Math.floor( c / 8 );
+
+					const hex = palette[ c + 1 ];
+					const r = hex >> 0 & 0xff;
+					const g = hex >> 8 & 0xff;
+					const b = hex >> 16 & 0xff;
+					context.fillStyle = `rgba(${r},${g},${b},1)`;
+					context.fillRect( x, 31 - y, 1, 1 );
+
+				}
+
+			}
+			*/
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_loader_vrml.ts b/examples-testing/examples/webgl_loader_vrml.ts
new file mode 100644
index 000000000..fecf4bb45
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_vrml.ts
@@ -0,0 +1,118 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats, controls, loader;
+
+const params = {
+    asset: 'house',
+};
+
+const assets = [
+    'creaseAngle',
+    'crystal',
+    'house',
+    'elevationGrid1',
+    'elevationGrid2',
+    'extrusion1',
+    'extrusion2',
+    'extrusion3',
+    'lines',
+    'linesTransparent',
+    'meshWithLines',
+    'meshWithTexture',
+    'pixelTexture',
+    'points',
+];
+
+let vrmlScene;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1e10);
+    camera.position.set(-10, 5, 10);
+
+    scene = new THREE.Scene();
+    scene.add(camera);
+
+    // light
+
+    const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
+    scene.add(ambientLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
+    dirLight.position.set(200, 200, 200);
+    scene.add(dirLight);
+
+    loader = new VRMLLoader();
+    loadAsset(params.asset);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 200;
+    controls.enableDamping = true;
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'asset', assets).onChange(function (value) {
+        if (vrmlScene) {
+            vrmlScene.traverse(function (object) {
+                if (object.material) object.material.dispose();
+                if (object.material && object.material.map) object.material.map.dispose();
+                if (object.geometry) object.geometry.dispose();
+            });
+
+            scene.remove(vrmlScene);
+        }
+
+        loadAsset(value);
+    });
+}
+
+function loadAsset(asset) {
+    loader.load('models/vrml/' + asset + '.wrl', function (object) {
+        vrmlScene = object;
+        scene.add(object);
+        controls.reset();
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update(); // to support damping
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_loader_vtk.ts b/examples-testing/examples/webgl_loader_vtk.ts
new file mode 100644
index 000000000..dfc798657
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_vtk.ts
@@ -0,0 +1,123 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { VTKLoader } from 'three/addons/loaders/VTKLoader.js';
+
+let stats;
+
+let camera, controls, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
+    camera.position.z = 0.2;
+
+    scene = new THREE.Scene();
+
+    scene.add(camera);
+
+    // light
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x000000, 3);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
+    dirLight.position.set(2, 2, 2);
+    scene.add(dirLight);
+
+    const loader = new VTKLoader();
+    loader.load('models/vtk/bunny.vtk', function (geometry) {
+        geometry.center();
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(-0.075, 0.005, 0);
+        mesh.scale.multiplyScalar(0.2);
+        scene.add(mesh);
+    });
+
+    const loader1 = new VTKLoader();
+    loader1.load('models/vtk/cube_ascii.vtp', function (geometry) {
+        geometry.computeVertexNormals();
+        geometry.center();
+
+        const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.set(-0.025, 0, 0);
+        mesh.scale.multiplyScalar(0.01);
+
+        scene.add(mesh);
+    });
+
+    const loader2 = new VTKLoader();
+    loader2.load('models/vtk/cube_binary.vtp', function (geometry) {
+        geometry.computeVertexNormals();
+        geometry.center();
+
+        const material = new THREE.MeshLambertMaterial({ color: 0x0000ff });
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.set(0.025, 0, 0);
+        mesh.scale.multiplyScalar(0.01);
+
+        scene.add(mesh);
+    });
+
+    const loader3 = new VTKLoader();
+    loader3.load('models/vtk/cube_no_compression.vtp', function (geometry) {
+        geometry.computeVertexNormals();
+        geometry.center();
+
+        const material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.set(0.075, 0, 0);
+        mesh.scale.multiplyScalar(0.01);
+
+        scene.add(mesh);
+    });
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    controls = new TrackballControls(camera, renderer.domElement);
+    controls.minDistance = 0.1;
+    controls.maxDistance = 0.5;
+    controls.rotateSpeed = 5.0;
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    controls.handleResize();
+}
+
+function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_loader_xyz.ts b/examples-testing/examples/webgl_loader_xyz.ts
new file mode 100644
index 000000000..90e009840
--- /dev/null
+++ b/examples-testing/examples/webgl_loader_xyz.ts
@@ -0,0 +1,62 @@
+import * as THREE from 'three';
+
+import { XYZLoader } from 'three/addons/loaders/XYZLoader.js';
+
+let camera, scene, renderer, clock;
+
+let points;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(10, 7, 10);
+
+    scene = new THREE.Scene();
+    scene.add(camera);
+    camera.lookAt(scene.position);
+
+    clock = new THREE.Clock();
+
+    const loader = new XYZLoader();
+    loader.load('models/xyz/helix_201.xyz', function (geometry) {
+        geometry.center();
+
+        const vertexColors = geometry.hasAttribute('color') === true;
+
+        const material = new THREE.PointsMaterial({ size: 0.1, vertexColors: vertexColors });
+
+        points = new THREE.Points(geometry, material);
+        scene.add(points);
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (points) {
+        points.rotation.x += delta * 0.2;
+        points.rotation.y += delta * 0.5;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_lod.ts b/examples-testing/examples/webgl_lod.ts
new file mode 100644
index 000000000..0bb9e7be0
--- /dev/null
+++ b/examples-testing/examples/webgl_lod.ts
@@ -0,0 +1,88 @@
+import * as THREE from 'three';
+
+import { FlyControls } from 'three/addons/controls/FlyControls.js';
+
+let container;
+
+let camera, scene, renderer, controls;
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 15000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 15000);
+
+    const pointLight = new THREE.PointLight(0xff2200, 3, 0, 0);
+    pointLight.position.set(0, 0, 0);
+    scene.add(pointLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(0, 0, 1).normalize();
+    scene.add(dirLight);
+
+    const geometry = [
+        [new THREE.IcosahedronGeometry(100, 16), 50],
+        [new THREE.IcosahedronGeometry(100, 8), 300],
+        [new THREE.IcosahedronGeometry(100, 4), 1000],
+        [new THREE.IcosahedronGeometry(100, 2), 2000],
+        [new THREE.IcosahedronGeometry(100, 1), 8000],
+    ];
+
+    const material = new THREE.MeshLambertMaterial({ color: 0xffffff, wireframe: true });
+
+    for (let j = 0; j < 1000; j++) {
+        const lod = new THREE.LOD();
+
+        for (let i = 0; i < geometry.length; i++) {
+            const mesh = new THREE.Mesh(geometry[i][0], material);
+            mesh.scale.set(1.5, 1.5, 1.5);
+            mesh.updateMatrix();
+            mesh.matrixAutoUpdate = false;
+            lod.addLevel(mesh, geometry[i][1]);
+        }
+
+        lod.position.x = 10000 * (0.5 - Math.random());
+        lod.position.y = 7500 * (0.5 - Math.random());
+        lod.position.z = 10000 * (0.5 - Math.random());
+        lod.updateMatrix();
+        lod.matrixAutoUpdate = false;
+        scene.add(lod);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    controls = new FlyControls(camera, renderer.domElement);
+    controls.movementSpeed = 1000;
+    controls.rollSpeed = Math.PI / 10;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update(clock.getDelta());
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_marchingcubes.ts b/examples-testing/examples/webgl_marchingcubes.ts
new file mode 100644
index 000000000..d11df56a4
--- /dev/null
+++ b/examples-testing/examples/webgl_marchingcubes.ts
@@ -0,0 +1,311 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { MarchingCubes } from 'three/addons/objects/MarchingCubes.js';
+import { ToonShader1, ToonShader2, ToonShaderHatching, ToonShaderDotted } from 'three/addons/shaders/ToonShader.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let materials, current_material;
+
+let light, pointLight, ambientLight;
+
+let effect, resolution;
+
+let effectController;
+
+let time = 0;
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(-500, 500, 1500);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    // LIGHTS
+
+    light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0.5, 0.5, 1);
+    scene.add(light);
+
+    pointLight = new THREE.PointLight(0xff7c00, 3, 0, 0);
+    pointLight.position.set(0, 0, 100);
+    scene.add(pointLight);
+
+    ambientLight = new THREE.AmbientLight(0x323232, 3);
+    scene.add(ambientLight);
+
+    // MATERIALS
+
+    materials = generateMaterials();
+    current_material = 'shiny';
+
+    // MARCHING CUBES
+
+    resolution = 28;
+
+    effect = new MarchingCubes(resolution, materials[current_material], true, true, 100000);
+    effect.position.set(0, 0, 0);
+    effect.scale.set(700, 700, 700);
+
+    effect.enableUvs = false;
+    effect.enableColors = false;
+
+    scene.add(effect);
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // CONTROLS
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 500;
+    controls.maxDistance = 5000;
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // GUI
+
+    setupGui();
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function generateMaterials() {
+    // environment map
+
+    const path = 'textures/cube/SwedishRoyalCastle/';
+    const format = '.jpg';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const cubeTextureLoader = new THREE.CubeTextureLoader();
+
+    const reflectionCube = cubeTextureLoader.load(urls);
+    const refractionCube = cubeTextureLoader.load(urls);
+    refractionCube.mapping = THREE.CubeRefractionMapping;
+
+    // toons
+
+    const toonMaterial1 = createShaderMaterial(ToonShader1, light, ambientLight);
+    const toonMaterial2 = createShaderMaterial(ToonShader2, light, ambientLight);
+    const hatchingMaterial = createShaderMaterial(ToonShaderHatching, light, ambientLight);
+    const dottedMaterial = createShaderMaterial(ToonShaderDotted, light, ambientLight);
+
+    const texture = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const materials = {
+        shiny: new THREE.MeshStandardMaterial({
+            color: 0x9c0000,
+            envMap: reflectionCube,
+            roughness: 0.1,
+            metalness: 1.0,
+        }),
+        chrome: new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: reflectionCube }),
+        liquid: new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: refractionCube, refractionRatio: 0.85 }),
+        matte: new THREE.MeshPhongMaterial({ specular: 0x494949, shininess: 1 }),
+        flat: new THREE.MeshLambertMaterial({
+            /*TODO flatShading: true */
+        }),
+        textured: new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0x111111, shininess: 1, map: texture }),
+        colors: new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0xffffff, shininess: 2, vertexColors: true }),
+        multiColors: new THREE.MeshPhongMaterial({ shininess: 2, vertexColors: true }),
+        plastic: new THREE.MeshPhongMaterial({ specular: 0xc1c1c1, shininess: 250 }),
+        toon1: toonMaterial1,
+        toon2: toonMaterial2,
+        hatching: hatchingMaterial,
+        dotted: dottedMaterial,
+    };
+
+    return materials;
+}
+
+function createShaderMaterial(shader, light, ambientLight) {
+    const u = THREE.UniformsUtils.clone(shader.uniforms);
+
+    const vs = shader.vertexShader;
+    const fs = shader.fragmentShader;
+
+    const material = new THREE.ShaderMaterial({ uniforms: u, vertexShader: vs, fragmentShader: fs });
+
+    material.uniforms['uDirLightPos'].value = light.position;
+    material.uniforms['uDirLightColor'].value = light.color;
+
+    material.uniforms['uAmbientLightColor'].value = ambientLight.color;
+
+    return material;
+}
+
+//
+
+function setupGui() {
+    const createHandler = function (id) {
+        return function () {
+            current_material = id;
+
+            effect.material = materials[id];
+            effect.enableUvs = current_material === 'textured' ? true : false;
+            effect.enableColors = current_material === 'colors' || current_material === 'multiColors' ? true : false;
+        };
+    };
+
+    effectController = {
+        material: 'shiny',
+
+        speed: 1.0,
+        numBlobs: 10,
+        resolution: 28,
+        isolation: 80,
+
+        floor: true,
+        wallx: false,
+        wallz: false,
+
+        dummy: function () {},
+    };
+
+    let h;
+
+    const gui = new GUI();
+
+    // material (type)
+
+    h = gui.addFolder('Materials');
+
+    for (const m in materials) {
+        effectController[m] = createHandler(m);
+        h.add(effectController, m).name(m);
+    }
+
+    // simulation
+
+    h = gui.addFolder('Simulation');
+
+    h.add(effectController, 'speed', 0.1, 8.0, 0.05);
+    h.add(effectController, 'numBlobs', 1, 50, 1);
+    h.add(effectController, 'resolution', 14, 100, 1);
+    h.add(effectController, 'isolation', 10, 300, 1);
+
+    h.add(effectController, 'floor');
+    h.add(effectController, 'wallx');
+    h.add(effectController, 'wallz');
+}
+
+// this controls content of marching cubes voxel field
+
+function updateCubes(object, time, numblobs, floor, wallx, wallz) {
+    object.reset();
+
+    // fill the field with some metaballs
+
+    const rainbow = [
+        new THREE.Color(0xff0000),
+        new THREE.Color(0xffbb00),
+        new THREE.Color(0xffff00),
+        new THREE.Color(0x00ff00),
+        new THREE.Color(0x0000ff),
+        new THREE.Color(0x9400bd),
+        new THREE.Color(0xc800eb),
+    ];
+    const subtract = 12;
+    const strength = 1.2 / ((Math.sqrt(numblobs) - 1) / 4 + 1);
+
+    for (let i = 0; i < numblobs; i++) {
+        const ballx = Math.sin(i + 1.26 * time * (1.03 + 0.5 * Math.cos(0.21 * i))) * 0.27 + 0.5;
+        const bally = Math.abs(Math.cos(i + 1.12 * time * Math.cos(1.22 + 0.1424 * i))) * 0.77; // dip into the floor
+        const ballz = Math.cos(i + 1.32 * time * 0.1 * Math.sin(0.92 + 0.53 * i)) * 0.27 + 0.5;
+
+        if (current_material === 'multiColors') {
+            object.addBall(ballx, bally, ballz, strength, subtract, rainbow[i % 7]);
+        } else {
+            object.addBall(ballx, bally, ballz, strength, subtract);
+        }
+    }
+
+    if (floor) object.addPlaneY(2, 12);
+    if (wallz) object.addPlaneZ(2, 12);
+    if (wallx) object.addPlaneX(2, 12);
+
+    object.update();
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    time += delta * effectController.speed * 0.5;
+
+    // marching cubes
+
+    if (effectController.resolution !== resolution) {
+        resolution = effectController.resolution;
+        effect.init(Math.floor(resolution));
+    }
+
+    if (effectController.isolation !== effect.isolation) {
+        effect.isolation = effectController.isolation;
+    }
+
+    updateCubes(
+        effect,
+        time,
+        effectController.numBlobs,
+        effectController.floor,
+        effectController.wallx,
+        effectController.wallz,
+    );
+
+    // render
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_alphahash.ts b/examples-testing/examples/webgl_materials_alphahash.ts
new file mode 100644
index 000000000..1ecf95f26
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_alphahash.ts
@@ -0,0 +1,178 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, controls, stats, mesh, material;
+
+let composer, renderPass, taaRenderPass, outputPass;
+
+let needsUpdate = false;
+
+const amount = parseInt(window.location.search.slice(1)) || 3;
+const count = Math.pow(amount, 3);
+
+const color = new THREE.Color();
+
+const params = {
+    alpha: 0.5,
+    alphaHash: true,
+    taa: true,
+    sampleLevel: 2,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(amount, amount, amount);
+    camera.lookAt(0, 0, 0);
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.IcosahedronGeometry(0.5, 3);
+
+    material = new THREE.MeshStandardMaterial({
+        color: 0xffffff,
+        alphaHash: params.alphaHash,
+        opacity: params.alpha,
+    });
+
+    mesh = new THREE.InstancedMesh(geometry, material, count);
+
+    let i = 0;
+    const offset = (amount - 1) / 2;
+
+    const matrix = new THREE.Matrix4();
+
+    for (let x = 0; x < amount; x++) {
+        for (let y = 0; y < amount; y++) {
+            for (let z = 0; z < amount; z++) {
+                matrix.setPosition(offset - x, offset - y, offset - z);
+
+                mesh.setMatrixAt(i, matrix);
+                mesh.setColorAt(i, color.setHex(Math.random() * 0xffffff));
+
+                i++;
+            }
+        }
+    }
+
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene.environment = pmremGenerator.fromScene(environment).texture;
+    environment.dispose();
+
+    //
+
+    composer = new EffectComposer(renderer);
+
+    renderPass = new RenderPass(scene, camera);
+    renderPass.enabled = false;
+
+    taaRenderPass = new TAARenderPass(scene, camera);
+
+    outputPass = new OutputPass();
+
+    composer.addPass(renderPass);
+    composer.addPass(taaRenderPass);
+    composer.addPass(outputPass);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enablePan = false;
+
+    controls.addEventListener('change', () => (needsUpdate = true));
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'alpha', 0, 1).onChange(onMaterialUpdate);
+    gui.add(params, 'alphaHash').onChange(onMaterialUpdate);
+
+    const taaFolder = gui.addFolder('Temporal Anti-Aliasing');
+
+    taaFolder
+        .add(params, 'taa')
+        .name('enabled')
+        .onChange(() => {
+            renderPass.enabled = !params.taa;
+            taaRenderPass.enabled = params.taa;
+
+            sampleLevelCtrl.enable(params.taa);
+
+            needsUpdate = true;
+        });
+
+    const sampleLevelCtrl = taaFolder.add(params, 'sampleLevel', 0, 6, 1).onChange(() => (needsUpdate = true));
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+
+    needsUpdate = true;
+}
+
+function onMaterialUpdate() {
+    material.opacity = params.alpha;
+    material.alphaHash = params.alphaHash;
+    material.transparent = !params.alphaHash;
+    material.depthWrite = params.alphaHash;
+
+    material.needsUpdate = true;
+    needsUpdate = true;
+}
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    if (needsUpdate) {
+        taaRenderPass.accumulate = false;
+        taaRenderPass.sampleLevel = 0;
+
+        needsUpdate = false;
+    } else {
+        taaRenderPass.accumulate = true;
+        taaRenderPass.sampleLevel = params.sampleLevel;
+    }
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_materials_blending.ts b/examples-testing/examples/webgl_materials_blending.ts
new file mode 100644
index 000000000..11cc009bc
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_blending.ts
@@ -0,0 +1,147 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let mapBg;
+
+const textureLoader = new THREE.TextureLoader();
+
+init();
+
+function init() {
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 600;
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    // BACKGROUND
+
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    canvas.width = canvas.height = 128;
+    ctx.fillStyle = '#ddd';
+    ctx.fillRect(0, 0, 128, 128);
+    ctx.fillStyle = '#555';
+    ctx.fillRect(0, 0, 64, 64);
+    ctx.fillStyle = '#999';
+    ctx.fillRect(32, 32, 32, 32);
+    ctx.fillStyle = '#555';
+    ctx.fillRect(64, 64, 64, 64);
+    ctx.fillStyle = '#777';
+    ctx.fillRect(96, 96, 32, 32);
+
+    mapBg = new THREE.CanvasTexture(canvas);
+    mapBg.colorSpace = THREE.SRGBColorSpace;
+    mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping;
+    mapBg.repeat.set(64, 32);
+
+    scene.background = mapBg;
+
+    // OBJECTS
+
+    const blendings = [
+        { name: 'No', constant: THREE.NoBlending },
+        { name: 'Normal', constant: THREE.NormalBlending },
+        { name: 'Additive', constant: THREE.AdditiveBlending },
+        { name: 'Subtractive', constant: THREE.SubtractiveBlending },
+        { name: 'Multiply', constant: THREE.MultiplyBlending },
+    ];
+
+    const assignSRGB = texture => {
+        texture.colorSpace = THREE.SRGBColorSpace;
+    };
+
+    const map0 = textureLoader.load('textures/uv_grid_opengl.jpg', assignSRGB);
+    const map1 = textureLoader.load('textures/sprite0.jpg', assignSRGB);
+    const map2 = textureLoader.load('textures/sprite0.png', assignSRGB);
+    const map3 = textureLoader.load('textures/lensflare/lensflare0.png', assignSRGB);
+    const map4 = textureLoader.load('textures/lensflare/lensflare0_alpha.png', assignSRGB);
+
+    const geo1 = new THREE.PlaneGeometry(100, 100);
+    const geo2 = new THREE.PlaneGeometry(100, 25);
+
+    addImageRow(map0, 300);
+    addImageRow(map1, 150);
+    addImageRow(map2, 0);
+    addImageRow(map3, -150);
+    addImageRow(map4, -300);
+
+    function addImageRow(map, y) {
+        for (let i = 0; i < blendings.length; i++) {
+            const blending = blendings[i];
+
+            const material = new THREE.MeshBasicMaterial({ map: map });
+            material.transparent = true;
+            material.blending = blending.constant;
+
+            const x = (i - blendings.length / 2) * 110;
+            const z = 0;
+
+            let mesh = new THREE.Mesh(geo1, material);
+            mesh.position.set(x, y, z);
+            scene.add(mesh);
+
+            mesh = new THREE.Mesh(geo2, generateLabelMaterial(blending.name));
+            mesh.position.set(x, y - 75, z);
+            scene.add(mesh);
+        }
+    }
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    const SCREEN_WIDTH = window.innerWidth;
+    const SCREEN_HEIGHT = window.innerHeight;
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+}
+
+function generateLabelMaterial(text) {
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    canvas.width = 128;
+    canvas.height = 32;
+
+    ctx.fillStyle = 'rgba( 0, 0, 0, 0.95 )';
+    ctx.fillRect(0, 0, 128, 32);
+
+    ctx.fillStyle = 'white';
+    ctx.font = 'bold 12pt arial';
+    ctx.fillText(text, 10, 22);
+
+    const map = new THREE.CanvasTexture(canvas);
+    map.colorSpace = THREE.SRGBColorSpace;
+
+    const material = new THREE.MeshBasicMaterial({ map: map, transparent: true });
+
+    return material;
+}
+
+function animate() {
+    const time = Date.now() * 0.00025;
+    const ox = (time * -0.01 * mapBg.repeat.x) % 1;
+    const oy = (time * -0.01 * mapBg.repeat.y) % 1;
+
+    mapBg.offset.set(ox, oy);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_blending_custom.ts b/examples-testing/examples/webgl_materials_blending_custom.ts
new file mode 100644
index 000000000..072447426
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_blending_custom.ts
@@ -0,0 +1,214 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+let mapBg;
+const materials = [];
+
+const params = {
+    blendEquation: THREE.AddEquation,
+};
+
+const equations = {
+    Add: THREE.AddEquation,
+    Subtract: THREE.SubtractEquation,
+    ReverseSubtract: THREE.ReverseSubtractEquation,
+    Min: THREE.MinEquation,
+    Max: THREE.MaxEquation,
+};
+
+init();
+
+function init() {
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 700;
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    // BACKGROUND
+
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    canvas.width = canvas.height = 128;
+    ctx.fillStyle = '#ddd';
+    ctx.fillRect(0, 0, 128, 128);
+    ctx.fillStyle = '#555';
+    ctx.fillRect(0, 0, 64, 64);
+    ctx.fillStyle = '#999';
+    ctx.fillRect(32, 32, 32, 32);
+    ctx.fillStyle = '#555';
+    ctx.fillRect(64, 64, 64, 64);
+    ctx.fillStyle = '#777';
+    ctx.fillRect(96, 96, 32, 32);
+
+    mapBg = new THREE.CanvasTexture(canvas);
+    mapBg.colorSpace = THREE.SRGBColorSpace;
+    mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping;
+    mapBg.repeat.set(64, 32);
+
+    scene.background = mapBg;
+
+    // FOREGROUND OBJECTS
+
+    const src = [
+        { name: 'Zero', constant: THREE.ZeroFactor },
+        { name: 'One', constant: THREE.OneFactor },
+        { name: 'SrcColor', constant: THREE.SrcColorFactor },
+        { name: 'OneMinusSrcColor', constant: THREE.OneMinusSrcColorFactor },
+        { name: 'SrcAlpha', constant: THREE.SrcAlphaFactor },
+        { name: 'OneMinusSrcAlpha', constant: THREE.OneMinusSrcAlphaFactor },
+        { name: 'DstAlpha', constant: THREE.DstAlphaFactor },
+        { name: 'OneMinusDstAlpha', constant: THREE.OneMinusDstAlphaFactor },
+        { name: 'DstColor', constant: THREE.DstColorFactor },
+        { name: 'OneMinusDstColor', constant: THREE.OneMinusDstColorFactor },
+        { name: 'SrcAlphaSaturate', constant: THREE.SrcAlphaSaturateFactor },
+    ];
+
+    const dst = [
+        { name: 'Zero', constant: THREE.ZeroFactor },
+        { name: 'One', constant: THREE.OneFactor },
+        { name: 'SrcColor', constant: THREE.SrcColorFactor },
+        { name: 'OneMinusSrcColor', constant: THREE.OneMinusSrcColorFactor },
+        { name: 'SrcAlpha', constant: THREE.SrcAlphaFactor },
+        { name: 'OneMinusSrcAlpha', constant: THREE.OneMinusSrcAlphaFactor },
+        { name: 'DstAlpha', constant: THREE.DstAlphaFactor },
+        { name: 'OneMinusDstAlpha', constant: THREE.OneMinusDstAlphaFactor },
+        { name: 'DstColor', constant: THREE.DstColorFactor },
+        { name: 'OneMinusDstColor', constant: THREE.OneMinusDstColorFactor },
+    ];
+
+    const geo1 = new THREE.PlaneGeometry(100, 100);
+    const geo2 = new THREE.PlaneGeometry(100, 25);
+
+    const texture = new THREE.TextureLoader().load('textures/lensflare/lensflare0_alpha.png');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    for (let i = 0; i < dst.length; i++) {
+        const blendDst = dst[i];
+
+        for (let j = 0; j < src.length; j++) {
+            const blendSrc = src[j];
+
+            const material = new THREE.MeshBasicMaterial({ map: texture });
+            material.transparent = true;
+
+            material.blending = THREE.CustomBlending;
+            material.blendSrc = blendSrc.constant;
+            material.blendDst = blendDst.constant;
+            material.blendEquation = THREE.AddEquation;
+
+            const x = (j - src.length / 2) * 110;
+            const z = 0;
+            const y = (i - dst.length / 2) * 110 + 50;
+
+            const mesh = new THREE.Mesh(geo1, material);
+            mesh.position.set(x, -y, z);
+            mesh.matrixAutoUpdate = false;
+            mesh.updateMatrix();
+            scene.add(mesh);
+
+            materials.push(material);
+        }
+    }
+
+    for (let j = 0; j < src.length; j++) {
+        const blendSrc = src[j];
+
+        const x = (j - src.length / 2) * 110;
+        const z = 0;
+        const y = (0 - dst.length / 2) * 110 + 50;
+
+        const mesh = new THREE.Mesh(geo2, generateLabelMaterial(blendSrc.name, 'rgba( 0, 150, 0, 1 )'));
+        mesh.position.set(x, -(y - 70), z);
+        mesh.matrixAutoUpdate = false;
+        mesh.updateMatrix();
+        scene.add(mesh);
+    }
+
+    for (let i = 0; i < dst.length; i++) {
+        const blendDst = dst[i];
+
+        const x = (0 - src.length / 2) * 110 - 125;
+        const z = 0;
+        const y = (i - dst.length / 2) * 110 + 165;
+
+        const mesh = new THREE.Mesh(geo2, generateLabelMaterial(blendDst.name, 'rgba( 150, 0, 0, 1 )'));
+        mesh.position.set(x, -(y - 120), z);
+        mesh.matrixAutoUpdate = false;
+        mesh.updateMatrix();
+        scene.add(mesh);
+    }
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+
+    // GUI
+
+    //
+    const gui = new GUI({ width: 300 });
+
+    gui.add(params, 'blendEquation', equations).onChange(updateBlendEquation);
+    gui.open();
+}
+
+//
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+//
+
+function generateLabelMaterial(text, bg) {
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    canvas.width = 128;
+    canvas.height = 32;
+
+    ctx.fillStyle = bg;
+    ctx.fillRect(0, 0, 128, 32);
+
+    ctx.fillStyle = 'white';
+    ctx.font = 'bold 11pt arial';
+    ctx.fillText(text, 8, 22);
+
+    const map = new THREE.CanvasTexture(canvas);
+    map.colorSpace = THREE.SRGBColorSpace;
+
+    const material = new THREE.MeshBasicMaterial({ map: map, transparent: true });
+    return material;
+}
+
+function updateBlendEquation(value) {
+    for (const material of materials) {
+        material.blendEquation = value;
+    }
+}
+
+function animate() {
+    const time = Date.now() * 0.00025;
+    const ox = (time * -0.01 * mapBg.repeat.x) % 1;
+    const oy = (time * -0.01 * mapBg.repeat.y) % 1;
+
+    mapBg.offset.set(ox, oy);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_bumpmap.ts b/examples-testing/examples/webgl_materials_bumpmap.ts
new file mode 100644
index 000000000..d954fab7e
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_bumpmap.ts
@@ -0,0 +1,140 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let container, stats, loader;
+
+let camera, scene, renderer;
+
+let mesh;
+
+let spotLight;
+
+let mouseX = 0;
+let mouseY = 0;
+
+let targetX = 0;
+let targetY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 12;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x060708);
+
+    // LIGHTS
+
+    scene.add(new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3));
+
+    spotLight = new THREE.SpotLight(0xffffde, 200);
+    spotLight.position.set(3.5, 0, 7);
+    scene.add(spotLight);
+
+    spotLight.castShadow = true;
+
+    spotLight.shadow.mapSize.width = 2048;
+    spotLight.shadow.mapSize.height = 2048;
+
+    spotLight.shadow.camera.near = 2;
+    spotLight.shadow.camera.far = 15;
+
+    spotLight.shadow.camera.fov = 40;
+
+    spotLight.shadow.bias = -0.005;
+
+    //
+
+    const mapHeight = new THREE.TextureLoader().load(
+        'models/gltf/LeePerrySmith/Infinite-Level_02_Disp_NoSmoothUV-4096.jpg',
+    );
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0x9c6e49,
+        specular: 0x666666,
+        shininess: 25,
+        bumpMap: mapHeight,
+        bumpScale: 10,
+    });
+
+    loader = new GLTFLoader();
+    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        createScene(gltf.scene.children[0].geometry, 1, material);
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    renderer.shadowMap.enabled = true;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // EVENTS
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createScene(geometry, scale, material) {
+    mesh = new THREE.Mesh(geometry, material);
+
+    mesh.position.y = -0.5;
+    mesh.scale.set(scale, scale, scale);
+
+    mesh.castShadow = true;
+    mesh.receiveShadow = true;
+
+    scene.add(mesh);
+}
+
+//
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    targetX = mouseX * 0.001;
+    targetY = mouseY * 0.001;
+
+    if (mesh) {
+        mesh.rotation.y += 0.05 * (targetX - mesh.rotation.y);
+        mesh.rotation.x += 0.05 * (targetY - mesh.rotation.x);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_car.ts b/examples-testing/examples/webgl_materials_car.ts
new file mode 100644
index 000000000..e810f7b7d
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_car.ts
@@ -0,0 +1,167 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer;
+let stats;
+
+let grid;
+let controls;
+
+const wheels = [];
+
+function init() {
+    const container = document.getElementById('container');
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 0.85;
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(4.25, 1.4, -4.5);
+
+    controls = new OrbitControls(camera, container);
+    controls.maxDistance = 9;
+    controls.maxPolarAngle = THREE.MathUtils.degToRad(90);
+    controls.target.set(0, 0.5, 0);
+    controls.update();
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x333333);
+    scene.environment = new RGBELoader().load('textures/equirectangular/venice_sunset_1k.hdr');
+    scene.environment.mapping = THREE.EquirectangularReflectionMapping;
+    scene.fog = new THREE.Fog(0x333333, 10, 15);
+
+    grid = new THREE.GridHelper(20, 40, 0xffffff, 0xffffff);
+    grid.material.opacity = 0.2;
+    grid.material.depthWrite = false;
+    grid.material.transparent = true;
+    scene.add(grid);
+
+    // materials
+
+    const bodyMaterial = new THREE.MeshPhysicalMaterial({
+        color: 0xff0000,
+        metalness: 1.0,
+        roughness: 0.5,
+        clearcoat: 1.0,
+        clearcoatRoughness: 0.03,
+    });
+
+    const detailsMaterial = new THREE.MeshStandardMaterial({
+        color: 0xffffff,
+        metalness: 1.0,
+        roughness: 0.5,
+    });
+
+    const glassMaterial = new THREE.MeshPhysicalMaterial({
+        color: 0xffffff,
+        metalness: 0.25,
+        roughness: 0,
+        transmission: 1.0,
+    });
+
+    const bodyColorInput = document.getElementById('body-color');
+    bodyColorInput.addEventListener('input', function () {
+        bodyMaterial.color.set(this.value);
+    });
+
+    const detailsColorInput = document.getElementById('details-color');
+    detailsColorInput.addEventListener('input', function () {
+        detailsMaterial.color.set(this.value);
+    });
+
+    const glassColorInput = document.getElementById('glass-color');
+    glassColorInput.addEventListener('input', function () {
+        glassMaterial.color.set(this.value);
+    });
+
+    // Car
+
+    const shadow = new THREE.TextureLoader().load('models/gltf/ferrari_ao.png');
+
+    const dracoLoader = new DRACOLoader();
+    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
+
+    const loader = new GLTFLoader();
+    loader.setDRACOLoader(dracoLoader);
+
+    loader.load('models/gltf/ferrari.glb', function (gltf) {
+        const carModel = gltf.scene.children[0];
+
+        carModel.getObjectByName('body').material = bodyMaterial;
+
+        carModel.getObjectByName('rim_fl').material = detailsMaterial;
+        carModel.getObjectByName('rim_fr').material = detailsMaterial;
+        carModel.getObjectByName('rim_rr').material = detailsMaterial;
+        carModel.getObjectByName('rim_rl').material = detailsMaterial;
+        carModel.getObjectByName('trim').material = detailsMaterial;
+
+        carModel.getObjectByName('glass').material = glassMaterial;
+
+        wheels.push(
+            carModel.getObjectByName('wheel_fl'),
+            carModel.getObjectByName('wheel_fr'),
+            carModel.getObjectByName('wheel_rl'),
+            carModel.getObjectByName('wheel_rr'),
+        );
+
+        // shadow
+        const mesh = new THREE.Mesh(
+            new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4),
+            new THREE.MeshBasicMaterial({
+                map: shadow,
+                blending: THREE.MultiplyBlending,
+                toneMapped: false,
+                transparent: true,
+            }),
+        );
+        mesh.rotation.x = -Math.PI / 2;
+        mesh.renderOrder = 2;
+        carModel.add(mesh);
+
+        scene.add(carModel);
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+
+    const time = -performance.now() / 1000;
+
+    for (let i = 0; i < wheels.length; i++) {
+        wheels[i].rotation.x = time * Math.PI * 2;
+    }
+
+    grid.position.z = -time % 1;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
+
+init();
diff --git a/examples-testing/examples/webgl_materials_cubemap.ts b/examples-testing/examples/webgl_materials_cubemap.ts
new file mode 100644
index 000000000..5f2692751
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_cubemap.ts
@@ -0,0 +1,115 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let pointLight;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 13;
+
+    //cubemap
+    const path = 'textures/cube/SwedishRoyalCastle/';
+    const format = '.jpg';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
+    const refractionCube = new THREE.CubeTextureLoader().load(urls);
+    refractionCube.mapping = THREE.CubeRefractionMapping;
+
+    scene = new THREE.Scene();
+    scene.background = reflectionCube;
+
+    //lights
+    const ambient = new THREE.AmbientLight(0xffffff, 3);
+    scene.add(ambient);
+
+    pointLight = new THREE.PointLight(0xffffff, 200);
+    scene.add(pointLight);
+
+    //materials
+    const cubeMaterial3 = new THREE.MeshLambertMaterial({
+        color: 0xffaa00,
+        envMap: reflectionCube,
+        combine: THREE.MixOperation,
+        reflectivity: 0.3,
+    });
+    const cubeMaterial2 = new THREE.MeshLambertMaterial({
+        color: 0xfff700,
+        envMap: refractionCube,
+        refractionRatio: 0.95,
+    });
+    const cubeMaterial1 = new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: reflectionCube });
+
+    //models
+    const objLoader = new OBJLoader();
+
+    objLoader.setPath('models/obj/walt/');
+    objLoader.load('WaltHead.obj', function (object) {
+        const head = object.children[0];
+        head.scale.setScalar(0.1);
+        head.position.y = -3;
+        head.material = cubeMaterial1;
+
+        const head2 = head.clone();
+        head2.position.x = -6;
+        head2.material = cubeMaterial2;
+
+        const head3 = head.clone();
+        head3.position.x = 6;
+        head3.material = cubeMaterial3;
+
+        scene.add(head, head2, head3);
+    });
+
+    //renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enablePan = false;
+    controls.minPolarAngle = Math.PI / 4;
+    controls.maxPolarAngle = Math.PI / 1.5;
+
+    //stats
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_materials_cubemap_dynamic.ts b/examples-testing/examples/webgl_materials_cubemap_dynamic.ts
new file mode 100644
index 000000000..13a268901
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_cubemap_dynamic.ts
@@ -0,0 +1,115 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats;
+let cube, sphere, torus, material;
+
+let cubeCamera, cubeRenderTarget;
+
+let controls;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResized);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 75;
+
+    scene = new THREE.Scene();
+    scene.rotation.y = 0.5; // avoid flying objects occluding the sun
+
+    new RGBELoader().setPath('textures/equirectangular/').load('quarry_01_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+    });
+
+    //
+
+    cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
+    cubeRenderTarget.texture.type = THREE.HalfFloatType;
+
+    cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget);
+
+    //
+
+    material = new THREE.MeshStandardMaterial({
+        envMap: cubeRenderTarget.texture,
+        roughness: 0.05,
+        metalness: 1,
+    });
+
+    const gui = new GUI();
+    gui.add(material, 'roughness', 0, 1);
+    gui.add(material, 'metalness', 0, 1);
+    gui.add(renderer, 'toneMappingExposure', 0, 2).name('exposure');
+
+    sphere = new THREE.Mesh(new THREE.IcosahedronGeometry(15, 8), material);
+    scene.add(sphere);
+
+    const material2 = new THREE.MeshStandardMaterial({
+        roughness: 0.1,
+        metalness: 0,
+    });
+
+    cube = new THREE.Mesh(new THREE.BoxGeometry(15, 15, 15), material2);
+    scene.add(cube);
+
+    torus = new THREE.Mesh(new THREE.TorusKnotGeometry(8, 3, 128, 16), material2);
+    scene.add(torus);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+}
+
+function onWindowResized() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+function animate(msTime) {
+    const time = msTime / 1000;
+
+    cube.position.x = Math.cos(time) * 30;
+    cube.position.y = Math.sin(time) * 30;
+    cube.position.z = Math.sin(time) * 30;
+
+    cube.rotation.x += 0.02;
+    cube.rotation.y += 0.03;
+
+    torus.position.x = Math.cos(time + 10) * 30;
+    torus.position.y = Math.sin(time + 10) * 30;
+    torus.position.z = Math.sin(time + 10) * 30;
+
+    torus.rotation.x += 0.02;
+    torus.rotation.y += 0.03;
+
+    cubeCamera.update(renderer, scene);
+
+    controls.update();
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts b/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
new file mode 100644
index 000000000..944f4c18e
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
@@ -0,0 +1,119 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container;
+
+let camera, scene, renderer;
+
+init();
+
+//load customized cube texture
+async function loadCubeTextureWithMipmaps() {
+    const path = 'textures/cube/angus/';
+    const format = '.jpg';
+    const mipmaps = [];
+    const maxLevel = 8;
+
+    async function loadCubeTexture(urls) {
+        return new Promise(function (resolve) {
+            new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
+                resolve(cubeTexture);
+            });
+        });
+    }
+
+    // load mipmaps
+    const pendings = [];
+
+    for (let level = 0; level <= maxLevel; ++level) {
+        const urls = [];
+
+        for (let face = 0; face < 6; ++face) {
+            urls.push(path + 'cube_m0' + level + '_c0' + face + format);
+        }
+
+        const mipmapLevel = level;
+
+        pendings.push(
+            loadCubeTexture(urls).then(function (cubeTexture) {
+                mipmaps[mipmapLevel] = cubeTexture;
+            }),
+        );
+    }
+
+    await Promise.all(pendings);
+
+    const customizedCubeTexture = mipmaps.shift();
+    customizedCubeTexture.mipmaps = mipmaps;
+    customizedCubeTexture.colorSpace = THREE.SRGBColorSpace;
+    customizedCubeTexture.minFilter = THREE.LinearMipMapLinearFilter;
+    customizedCubeTexture.magFilter = THREE.LinearFilter;
+    customizedCubeTexture.generateMipmaps = false;
+    customizedCubeTexture.needsUpdate = true;
+
+    return customizedCubeTexture;
+}
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    loadCubeTextureWithMipmaps().then(function (cubeTexture) {
+        //model
+        const sphere = new THREE.SphereGeometry(100, 128, 128);
+
+        //manual mipmaps
+        let material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: cubeTexture });
+        material.name = 'manual mipmaps';
+
+        let mesh = new THREE.Mesh(sphere, material);
+        mesh.position.set(100, 0, 0);
+        scene.add(mesh);
+
+        //webgl mipmaps
+        material = material.clone();
+        material.name = 'auto mipmaps';
+
+        const autoCubeTexture = cubeTexture.clone();
+        autoCubeTexture.mipmaps = [];
+        autoCubeTexture.generateMipmaps = true;
+        autoCubeTexture.needsUpdate = true;
+
+        material.envMap = autoCubeTexture;
+
+        mesh = new THREE.Mesh(sphere, material);
+        mesh.position.set(-100, 0, 0);
+        scene.add(mesh);
+    });
+
+    //renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minPolarAngle = Math.PI / 4;
+    controls.maxPolarAngle = Math.PI / 1.5;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_cubemap_refraction.ts b/examples-testing/examples/webgl_materials_cubemap_refraction.ts
new file mode 100644
index 000000000..8c025071f
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_cubemap_refraction.ts
@@ -0,0 +1,126 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 100000);
+    camera.position.z = -4000;
+
+    //
+
+    const r = 'textures/cube/Park3Med/';
+
+    const urls = [r + 'px.jpg', r + 'nx.jpg', r + 'py.jpg', r + 'ny.jpg', r + 'pz.jpg', r + 'nz.jpg'];
+
+    const textureCube = new THREE.CubeTextureLoader().load(urls);
+    textureCube.mapping = THREE.CubeRefractionMapping;
+
+    scene = new THREE.Scene();
+    scene.background = textureCube;
+
+    // LIGHTS
+
+    const ambient = new THREE.AmbientLight(0xffffff, 3.5);
+    scene.add(ambient);
+
+    // material samples
+
+    const cubeMaterial3 = new THREE.MeshPhongMaterial({
+        color: 0xccddff,
+        envMap: textureCube,
+        refractionRatio: 0.98,
+        reflectivity: 0.9,
+    });
+    const cubeMaterial2 = new THREE.MeshPhongMaterial({ color: 0xccfffd, envMap: textureCube, refractionRatio: 0.985 });
+    const cubeMaterial1 = new THREE.MeshPhongMaterial({ color: 0xffffff, envMap: textureCube, refractionRatio: 0.98 });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const loader = new PLYLoader();
+    loader.load('models/ply/binary/Lucy100k.ply', function (geometry) {
+        createScene(geometry, cubeMaterial1, cubeMaterial2, cubeMaterial3);
+    });
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function createScene(geometry, m1, m2, m3) {
+    geometry.computeVertexNormals();
+
+    const s = 1.5;
+
+    let mesh = new THREE.Mesh(geometry, m1);
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry, m2);
+    mesh.position.x = -1500;
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry, m3);
+    mesh.position.x = 1500;
+    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
+    scene.add(mesh);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) * 4;
+    mouseY = (event.clientY - windowHalfY) * 4;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts b/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
new file mode 100644
index 000000000..599a1369b
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
@@ -0,0 +1,183 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container;
+let camera, scene, renderer;
+
+const CubemapFilterShader = {
+    name: 'CubemapFilterShader',
+
+    uniforms: {
+        cubeTexture: { value: null },
+        mipIndex: { value: 0 },
+    },
+
+    vertexShader: /* glsl */ `
+
+					varying vec3 vWorldDirection;
+
+					#include <common>
+
+					void main() {
+						vWorldDirection = transformDirection(position, modelMatrix);
+						#include <begin_vertex>
+						#include <project_vertex>
+						gl_Position.z = gl_Position.w; // set z to camera.far
+					}
+
+					`,
+
+    fragmentShader: /* glsl */ `
+
+					uniform samplerCube cubeTexture;
+					varying vec3 vWorldDirection;
+
+					uniform float mipIndex;
+
+					#include <common>
+
+					void main() {
+						vec3 cubeCoordinates = normalize(vWorldDirection);
+
+						// Colorize mip levels
+						vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
+						if (mipIndex == 0.0) color.rgb = vec3(1.0, 1.0, 1.0);
+						else if (mipIndex == 1.0) color.rgb = vec3(0.0, 0.0, 1.0);
+						else if (mipIndex == 2.0) color.rgb = vec3(0.0, 1.0, 1.0);
+						else if (mipIndex == 3.0) color.rgb = vec3(0.0, 1.0, 0.0);
+						else if (mipIndex == 4.0) color.rgb = vec3(1.0, 1.0, 0.0);
+
+						gl_FragColor = textureCube(cubeTexture, cubeCoordinates, 0.0) * color;
+					}
+
+					`,
+};
+
+init();
+
+async function loadCubeTexture(urls) {
+    return new Promise(function (resolve) {
+        new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
+            resolve(cubeTexture);
+        });
+    });
+}
+
+function allocateCubemapRenderTarget(cubeMapSize) {
+    const params = {
+        magFilter: THREE.LinearFilter,
+        minFilter: THREE.LinearMipMapLinearFilter,
+        generateMipmaps: false,
+        type: THREE.HalfFloatType,
+        format: THREE.RGBAFormat,
+        colorSpace: THREE.LinearSRGBColorSpace,
+        depthBuffer: false,
+    };
+
+    const rt = new THREE.WebGLCubeRenderTarget(cubeMapSize, params);
+
+    const mipLevels = Math.log(cubeMapSize) * Math.LOG2E + 1.0;
+    for (let i = 0; i < mipLevels; i++) rt.texture.mipmaps.push({});
+
+    rt.texture.mapping = THREE.CubeReflectionMapping;
+    return rt;
+}
+
+function renderToCubeTexture(cubeMapRenderTarget, sourceCubeTexture) {
+    const geometry = new THREE.BoxGeometry(5, 5, 5);
+
+    const material = new THREE.ShaderMaterial({
+        name: CubemapFilterShader.name,
+        uniforms: THREE.UniformsUtils.clone(CubemapFilterShader.uniforms),
+        vertexShader: CubemapFilterShader.vertexShader,
+        fragmentShader: CubemapFilterShader.fragmentShader,
+        side: THREE.BackSide,
+        blending: THREE.NoBlending,
+    });
+
+    material.uniforms.cubeTexture.value = sourceCubeTexture;
+
+    const mesh = new THREE.Mesh(geometry, material);
+    const cubeCamera = new THREE.CubeCamera(1, 10, cubeMapRenderTarget);
+    const mipmapCount = Math.floor(Math.log2(Math.max(cubeMapRenderTarget.width, cubeMapRenderTarget.height)));
+
+    for (let mipmap = 0; mipmap < mipmapCount; mipmap++) {
+        material.uniforms.mipIndex.value = mipmap;
+        material.needsUpdate = true;
+
+        cubeMapRenderTarget.viewport.set(
+            0,
+            0,
+            cubeMapRenderTarget.width >> mipmap,
+            cubeMapRenderTarget.height >> mipmap,
+        );
+
+        cubeCamera.activeMipmapLevel = mipmap;
+        cubeCamera.update(renderer, mesh);
+    }
+
+    mesh.geometry.dispose();
+    mesh.material.dispose();
+}
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // Create renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 500;
+
+    // Create controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minPolarAngle = Math.PI / 4;
+    controls.maxPolarAngle = Math.PI / 1.5;
+
+    window.addEventListener('resize', onWindowResize);
+
+    // Load a cube texture
+    const r = 'textures/cube/Park3Med/';
+    const urls = [r + 'px.jpg', r + 'nx.jpg', r + 'py.jpg', r + 'ny.jpg', r + 'pz.jpg', r + 'nz.jpg'];
+
+    loadCubeTexture(urls).then(cubeTexture => {
+        // Allocate a cube map render target
+        const cubeMapRenderTarget = allocateCubemapRenderTarget(512);
+
+        // Render to all the mip levels of cubeMapRenderTarget
+        renderToCubeTexture(cubeMapRenderTarget, cubeTexture);
+
+        // Create geometry
+        const sphere = new THREE.SphereGeometry(100, 128, 128);
+        let material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: cubeTexture });
+
+        let mesh = new THREE.Mesh(sphere, material);
+        mesh.position.set(-100, 0, 0);
+        scene.add(mesh);
+
+        material = material.clone();
+        material.envMap = cubeMapRenderTarget.texture;
+
+        mesh = new THREE.Mesh(sphere, material);
+        mesh.position.set(100, 0, 0);
+        scene.add(mesh);
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_displacementmap.ts b/examples-testing/examples/webgl_materials_displacementmap.ts
new file mode 100644
index 000000000..fd0be9a5e
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_displacementmap.ts
@@ -0,0 +1,224 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+
+let stats;
+let camera, scene, renderer, controls;
+
+const settings = {
+    metalness: 1.0,
+    roughness: 0.4,
+    ambientIntensity: 0.2,
+    aoMapIntensity: 1.0,
+    envMapIntensity: 1.0,
+    displacementScale: 2.436143, // from original model
+    normalScale: 1.0,
+};
+
+let mesh, material;
+
+let pointLight, ambientLight;
+
+const height = 500; // of camera frustum
+
+let r = 0.0;
+
+init();
+initGui();
+
+// Init gui
+function initGui() {
+    const gui = new GUI();
+    //let gui = gui.addFolder( "Material" );
+    gui.add(settings, 'metalness')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.metalness = value;
+        });
+
+    gui.add(settings, 'roughness')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.roughness = value;
+        });
+
+    gui.add(settings, 'aoMapIntensity')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.aoMapIntensity = value;
+        });
+
+    gui.add(settings, 'ambientIntensity')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            ambientLight.intensity = value;
+        });
+
+    gui.add(settings, 'envMapIntensity')
+        .min(0)
+        .max(3)
+        .onChange(function (value) {
+            material.envMapIntensity = value;
+        });
+
+    gui.add(settings, 'displacementScale')
+        .min(0)
+        .max(3.0)
+        .onChange(function (value) {
+            material.displacementScale = value;
+        });
+
+    gui.add(settings, 'normalScale')
+        .min(-1)
+        .max(1)
+        .onChange(function (value) {
+            material.normalScale.set(1, -1).multiplyScalar(value);
+        });
+}
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    scene = new THREE.Scene();
+
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera(-height * aspect, height * aspect, height, -height, 1, 10000);
+    camera.position.z = 1500;
+    scene.add(camera);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enableDamping = true;
+
+    // lights
+
+    ambientLight = new THREE.AmbientLight(0xffffff, settings.ambientIntensity);
+    scene.add(ambientLight);
+
+    pointLight = new THREE.PointLight(0xff0000, 1.5, 0, 0);
+    pointLight.position.z = 2500;
+    scene.add(pointLight);
+
+    const pointLight2 = new THREE.PointLight(0xff6666, 3, 0, 0);
+    camera.add(pointLight2);
+
+    const pointLight3 = new THREE.PointLight(0x0000ff, 1.5, 0, 0);
+    pointLight3.position.x = -1000;
+    pointLight3.position.z = 1000;
+    scene.add(pointLight3);
+
+    // env map
+
+    const path = 'textures/cube/SwedishRoyalCastle/';
+    const format = '.jpg';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
+
+    // textures
+
+    const textureLoader = new THREE.TextureLoader();
+    const normalMap = textureLoader.load('models/obj/ninja/normal.png');
+    const aoMap = textureLoader.load('models/obj/ninja/ao.jpg');
+    const displacementMap = textureLoader.load('models/obj/ninja/displacement.jpg');
+
+    // material
+
+    material = new THREE.MeshStandardMaterial({
+        color: 0xc1c1c1,
+        roughness: settings.roughness,
+        metalness: settings.metalness,
+
+        normalMap: normalMap,
+        normalScale: new THREE.Vector2(1, -1), // why does the normal map require negation in this case?
+
+        aoMap: aoMap,
+        aoMapIntensity: 1,
+
+        displacementMap: displacementMap,
+        displacementScale: settings.displacementScale,
+        displacementBias: -0.428408, // from original model
+
+        envMap: reflectionCube,
+        envMapIntensity: settings.envMapIntensity,
+
+        side: THREE.DoubleSide,
+    });
+
+    //
+
+    const loader = new OBJLoader();
+    loader.load('models/obj/ninja/ninjaHead_Low.obj', function (group) {
+        const geometry = group.children[0].geometry;
+        geometry.center();
+
+        mesh = new THREE.Mesh(geometry, material);
+        mesh.scale.multiplyScalar(25);
+        scene.add(mesh);
+    });
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera.left = -height * aspect;
+    camera.right = height * aspect;
+    camera.top = height;
+    camera.bottom = -height;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    controls.update();
+
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    pointLight.position.x = 2500 * Math.cos(r);
+    pointLight.position.z = 2500 * Math.sin(r);
+
+    r += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_envmaps.ts b/examples-testing/examples/webgl_materials_envmaps.ts
new file mode 100644
index 000000000..18a5542ed
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_envmaps.ts
@@ -0,0 +1,131 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let controls, camera, scene, renderer;
+let textureEquirec, textureCube;
+let sphereMesh, sphereMaterial, params;
+
+init();
+
+function init() {
+    // CAMERAS
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 2.5);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    // Textures
+
+    const loader = new THREE.CubeTextureLoader();
+    loader.setPath('textures/cube/Bridge2/');
+
+    textureCube = loader.load(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
+
+    const textureLoader = new THREE.TextureLoader();
+
+    textureEquirec = textureLoader.load('textures/2294472375_24a3b8ef46_o.jpg');
+    textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
+    textureEquirec.colorSpace = THREE.SRGBColorSpace;
+
+    scene.background = textureCube;
+
+    //
+
+    const geometry = new THREE.IcosahedronGeometry(1, 15);
+    sphereMaterial = new THREE.MeshBasicMaterial({ envMap: textureCube });
+    sphereMesh = new THREE.Mesh(geometry, sphereMaterial);
+    scene.add(sphereMesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1.5;
+    controls.maxDistance = 6;
+
+    //
+
+    params = {
+        Cube: function () {
+            scene.background = textureCube;
+
+            sphereMaterial.envMap = textureCube;
+            sphereMaterial.needsUpdate = true;
+        },
+        Equirectangular: function () {
+            scene.background = textureEquirec;
+
+            sphereMaterial.envMap = textureEquirec;
+            sphereMaterial.needsUpdate = true;
+        },
+        Refraction: false,
+        backgroundRotationX: false,
+        backgroundRotationY: false,
+        backgroundRotationZ: false,
+        syncMaterial: false,
+    };
+
+    const gui = new GUI({ width: 300 });
+    gui.add(params, 'Cube');
+    gui.add(params, 'Equirectangular');
+    gui.add(params, 'Refraction').onChange(function (value) {
+        if (value) {
+            textureEquirec.mapping = THREE.EquirectangularRefractionMapping;
+            textureCube.mapping = THREE.CubeRefractionMapping;
+        } else {
+            textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
+            textureCube.mapping = THREE.CubeReflectionMapping;
+        }
+
+        sphereMaterial.needsUpdate = true;
+    });
+    gui.add(params, 'backgroundRotationX');
+    gui.add(params, 'backgroundRotationY');
+    gui.add(params, 'backgroundRotationZ');
+    gui.add(params, 'syncMaterial');
+    gui.open();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    if (params.backgroundRotationX) {
+        scene.backgroundRotation.x += 0.001;
+    }
+
+    if (params.backgroundRotationY) {
+        scene.backgroundRotation.y += 0.001;
+    }
+
+    if (params.backgroundRotationZ) {
+        scene.backgroundRotation.z += 0.001;
+    }
+
+    if (params.syncMaterial) {
+        sphereMesh.material.envMapRotation.copy(scene.backgroundRotation);
+    }
+
+    camera.lookAt(scene.position);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_envmaps_exr.ts b/examples-testing/examples/webgl_materials_envmaps_exr.ts
new file mode 100644
index 000000000..c3f3f4f7d
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_envmaps_exr.ts
@@ -0,0 +1,153 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
+
+const params = {
+    envMap: 'EXR',
+    roughness: 0.0,
+    metalness: 0.0,
+    exposure: 1.0,
+    debug: false,
+};
+
+let container, stats;
+let camera, scene, renderer, controls;
+let torusMesh, planeMesh;
+let pngCubeRenderTarget, exrCubeRenderTarget;
+let pngBackground, exrBackground;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 120);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+    //
+
+    let geometry = new THREE.TorusKnotGeometry(18, 8, 150, 20);
+    let material = new THREE.MeshStandardMaterial({
+        metalness: params.metalness,
+        roughness: params.roughness,
+        envMapIntensity: 1.0,
+    });
+
+    torusMesh = new THREE.Mesh(geometry, material);
+    scene.add(torusMesh);
+
+    geometry = new THREE.PlaneGeometry(200, 200);
+    material = new THREE.MeshBasicMaterial();
+
+    planeMesh = new THREE.Mesh(geometry, material);
+    planeMesh.position.y = -50;
+    planeMesh.rotation.x = -Math.PI * 0.5;
+    scene.add(planeMesh);
+
+    THREE.DefaultLoadingManager.onLoad = function () {
+        pmremGenerator.dispose();
+    };
+
+    new EXRLoader().load('textures/piz_compressed.exr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        exrCubeRenderTarget = pmremGenerator.fromEquirectangular(texture);
+        exrBackground = texture;
+    });
+
+    new THREE.TextureLoader().load('textures/equirectangular.png', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        pngCubeRenderTarget = pmremGenerator.fromEquirectangular(texture);
+        pngBackground = texture;
+    });
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    pmremGenerator.compileEquirectangularShader();
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 50;
+    controls.maxDistance = 300;
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'envMap', ['EXR', 'PNG']);
+    gui.add(params, 'roughness', 0, 1, 0.01);
+    gui.add(params, 'metalness', 0, 1, 0.01);
+    gui.add(params, 'exposure', 0, 2, 0.01);
+    gui.add(params, 'debug');
+    gui.open();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    torusMesh.material.roughness = params.roughness;
+    torusMesh.material.metalness = params.metalness;
+
+    let newEnvMap = torusMesh.material.envMap;
+    let background = scene.background;
+
+    switch (params.envMap) {
+        case 'EXR':
+            newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
+            background = exrBackground;
+            break;
+        case 'PNG':
+            newEnvMap = pngCubeRenderTarget ? pngCubeRenderTarget.texture : null;
+            background = pngBackground;
+            break;
+    }
+
+    if (newEnvMap !== torusMesh.material.envMap) {
+        torusMesh.material.envMap = newEnvMap;
+        torusMesh.material.needsUpdate = true;
+
+        planeMesh.material.map = newEnvMap;
+        planeMesh.material.needsUpdate = true;
+    }
+
+    torusMesh.rotation.y += 0.005;
+    planeMesh.visible = params.debug;
+
+    scene.background = background;
+    renderer.toneMappingExposure = params.exposure;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts b/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
new file mode 100644
index 000000000..48e0077f4
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
@@ -0,0 +1,150 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+const params = {
+    height: 15,
+    radius: 100,
+    enabled: true,
+};
+
+let camera, scene, renderer, skybox;
+
+init().then(render);
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(-20, 7, 20);
+    camera.lookAt(0, 4, 0);
+
+    scene = new THREE.Scene();
+
+    const hdrLoader = new RGBELoader();
+    const envMap = await hdrLoader.loadAsync('textures/equirectangular/blouberg_sunrise_2_1k.hdr');
+    envMap.mapping = THREE.EquirectangularReflectionMapping;
+
+    skybox = new GroundedSkybox(envMap, params.height, params.radius);
+    skybox.position.y = params.height - 0.01;
+    scene.add(skybox);
+
+    scene.environment = envMap;
+
+    const dracoLoader = new DRACOLoader();
+    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
+
+    const loader = new GLTFLoader();
+    loader.setDRACOLoader(dracoLoader);
+
+    const shadow = new THREE.TextureLoader().load('models/gltf/ferrari_ao.png');
+
+    loader.load('models/gltf/ferrari.glb', function (gltf) {
+        const bodyMaterial = new THREE.MeshPhysicalMaterial({
+            color: 0x000000,
+            metalness: 1.0,
+            roughness: 0.8,
+            clearcoat: 1.0,
+            clearcoatRoughness: 0.2,
+        });
+
+        const detailsMaterial = new THREE.MeshStandardMaterial({
+            color: 0xffffff,
+            metalness: 1.0,
+            roughness: 0.5,
+        });
+
+        const glassMaterial = new THREE.MeshPhysicalMaterial({
+            color: 0xffffff,
+            metalness: 0.25,
+            roughness: 0,
+            transmission: 1.0,
+        });
+
+        const carModel = gltf.scene.children[0];
+        carModel.scale.multiplyScalar(4);
+        carModel.rotation.y = Math.PI;
+
+        carModel.getObjectByName('body').material = bodyMaterial;
+
+        carModel.getObjectByName('rim_fl').material = detailsMaterial;
+        carModel.getObjectByName('rim_fr').material = detailsMaterial;
+        carModel.getObjectByName('rim_rr').material = detailsMaterial;
+        carModel.getObjectByName('rim_rl').material = detailsMaterial;
+        carModel.getObjectByName('trim').material = detailsMaterial;
+
+        carModel.getObjectByName('glass').material = glassMaterial;
+
+        // shadow
+        const mesh = new THREE.Mesh(
+            new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4),
+            new THREE.MeshBasicMaterial({
+                map: shadow,
+                blending: THREE.MultiplyBlending,
+                toneMapped: false,
+                transparent: true,
+            }),
+        );
+        mesh.rotation.x = -Math.PI / 2;
+        carModel.add(mesh);
+
+        scene.add(carModel);
+
+        render();
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.target.set(0, 2, 0);
+    controls.maxPolarAngle = THREE.MathUtils.degToRad(90);
+    controls.maxDistance = 80;
+    controls.minDistance = 20;
+    controls.enablePan = false;
+    controls.update();
+
+    document.body.appendChild(renderer.domElement);
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'enabled')
+        .name('Grounded')
+        .onChange(function (value) {
+            if (value) {
+                scene.add(skybox);
+                scene.background = null;
+            } else {
+                scene.remove(skybox);
+                scene.background = scene.environment;
+            }
+
+            render();
+        });
+
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_envmaps_hdr.ts b/examples-testing/examples/webgl_materials_envmaps_hdr.ts
new file mode 100644
index 000000000..b4c6f64ef
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_envmaps_hdr.ts
@@ -0,0 +1,176 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
+import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
+import { DebugEnvironment } from 'three/addons/environments/DebugEnvironment.js';
+
+const params = {
+    envMap: 'HDR',
+    roughness: 0.0,
+    metalness: 0.0,
+    exposure: 1.0,
+    debug: false,
+};
+
+let container, stats;
+let camera, scene, renderer, controls;
+let torusMesh, planeMesh;
+let generatedCubeRenderTarget, ldrCubeRenderTarget, hdrCubeRenderTarget, rgbmCubeRenderTarget;
+let ldrCubeMap, hdrCubeMap, rgbmCubeMap;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 120);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x000000);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+    //
+
+    let geometry = new THREE.TorusKnotGeometry(18, 8, 150, 20);
+    // let geometry = new THREE.SphereGeometry( 26, 64, 32 );
+    let material = new THREE.MeshStandardMaterial({
+        color: 0xffffff,
+        metalness: params.metalness,
+        roughness: params.roughness,
+    });
+
+    torusMesh = new THREE.Mesh(geometry, material);
+    scene.add(torusMesh);
+
+    geometry = new THREE.PlaneGeometry(200, 200);
+    material = new THREE.MeshBasicMaterial();
+
+    planeMesh = new THREE.Mesh(geometry, material);
+    planeMesh.position.y = -50;
+    planeMesh.rotation.x = -Math.PI * 0.5;
+    scene.add(planeMesh);
+
+    THREE.DefaultLoadingManager.onLoad = function () {
+        pmremGenerator.dispose();
+    };
+
+    const hdrUrls = ['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'];
+    hdrCubeMap = new HDRCubeTextureLoader().setPath('./textures/cube/pisaHDR/').load(hdrUrls, function () {
+        hdrCubeRenderTarget = pmremGenerator.fromCubemap(hdrCubeMap);
+
+        hdrCubeMap.magFilter = THREE.LinearFilter;
+        hdrCubeMap.needsUpdate = true;
+    });
+
+    const ldrUrls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
+    ldrCubeMap = new THREE.CubeTextureLoader().setPath('./textures/cube/pisa/').load(ldrUrls, function () {
+        ldrCubeRenderTarget = pmremGenerator.fromCubemap(ldrCubeMap);
+    });
+
+    const rgbmUrls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
+    rgbmCubeMap = new RGBMLoader()
+        .setMaxRange(16)
+        .setPath('./textures/cube/pisaRGBM16/')
+        .loadCubemap(rgbmUrls, function () {
+            rgbmCubeRenderTarget = pmremGenerator.fromCubemap(rgbmCubeMap);
+        });
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    pmremGenerator.compileCubemapShader();
+
+    const envScene = new DebugEnvironment();
+    generatedCubeRenderTarget = pmremGenerator.fromScene(envScene);
+
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //renderer.toneMapping = ReinhardToneMapping;
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 50;
+    controls.maxDistance = 300;
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    gui.add(params, 'envMap', ['Generated', 'LDR', 'HDR', 'RGBM16']);
+    gui.add(params, 'roughness', 0, 1, 0.01);
+    gui.add(params, 'metalness', 0, 1, 0.01);
+    gui.add(params, 'exposure', 0, 2, 0.01);
+    gui.add(params, 'debug');
+    gui.open();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    torusMesh.material.roughness = params.roughness;
+    torusMesh.material.metalness = params.metalness;
+
+    let renderTarget, cubeMap;
+
+    switch (params.envMap) {
+        case 'Generated':
+            renderTarget = generatedCubeRenderTarget;
+            cubeMap = generatedCubeRenderTarget.texture;
+            break;
+        case 'LDR':
+            renderTarget = ldrCubeRenderTarget;
+            cubeMap = ldrCubeMap;
+            break;
+        case 'HDR':
+            renderTarget = hdrCubeRenderTarget;
+            cubeMap = hdrCubeMap;
+            break;
+        case 'RGBM16':
+            renderTarget = rgbmCubeRenderTarget;
+            cubeMap = rgbmCubeMap;
+            break;
+    }
+
+    const newEnvMap = renderTarget ? renderTarget.texture : null;
+
+    if (newEnvMap && newEnvMap !== torusMesh.material.envMap) {
+        torusMesh.material.envMap = newEnvMap;
+        torusMesh.material.needsUpdate = true;
+
+        planeMesh.material.map = newEnvMap;
+        planeMesh.material.needsUpdate = true;
+    }
+
+    torusMesh.rotation.y += 0.005;
+    planeMesh.visible = params.debug;
+
+    scene.background = cubeMap;
+    renderer.toneMappingExposure = params.exposure;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_modified.ts b/examples-testing/examples/webgl_materials_modified.ts
new file mode 100644
index 000000000..c6ee5af3c
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_modified.ts
@@ -0,0 +1,115 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 20;
+
+    scene = new THREE.Scene();
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        const geometry = gltf.scene.children[0].geometry;
+
+        let mesh = new THREE.Mesh(geometry, buildTwistMaterial(2.0));
+        mesh.position.x = -3.5;
+        mesh.position.y = -0.5;
+        scene.add(mesh);
+
+        mesh = new THREE.Mesh(geometry, buildTwistMaterial(-2.0));
+        mesh.position.x = 3.5;
+        mesh.position.y = -0.5;
+        scene.add(mesh);
+    });
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 10;
+    controls.maxDistance = 50;
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // EVENTS
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function buildTwistMaterial(amount) {
+    const material = new THREE.MeshNormalMaterial();
+    material.onBeforeCompile = function (shader) {
+        shader.uniforms.time = { value: 0 };
+
+        shader.vertexShader = 'uniform float time;\n' + shader.vertexShader;
+        shader.vertexShader = shader.vertexShader.replace(
+            '#include <begin_vertex>',
+            [
+                `float theta = sin( time + position.y ) / ${amount.toFixed(1)};`,
+                'float c = cos( theta );',
+                'float s = sin( theta );',
+                'mat3 m = mat3( c, 0, s, 0, 1, 0, -s, 0, c );',
+                'vec3 transformed = vec3( position ) * m;',
+                'vNormal = vNormal * m;',
+            ].join('\n'),
+        );
+
+        material.userData.shader = shader;
+    };
+
+    // Make sure WebGLRenderer doesnt reuse a single program
+
+    material.customProgramCacheKey = function () {
+        return amount.toFixed(1);
+    };
+
+    return material;
+}
+
+//
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    scene.traverse(function (child) {
+        if (child.isMesh) {
+            const shader = child.material.userData.shader;
+
+            if (shader) {
+                shader.uniforms.time.value = performance.now() / 1000;
+            }
+        }
+    });
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_normalmap_object_space.ts b/examples-testing/examples/webgl_materials_normalmap_object_space.ts
new file mode 100644
index 000000000..1fc6f8066
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_normalmap_object_space.ts
@@ -0,0 +1,82 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let renderer, scene, camera;
+
+init();
+
+function init() {
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(-10, 0, 23);
+    scene.add(camera);
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 10;
+    controls.maxDistance = 50;
+    controls.enablePan = false;
+
+    // ambient
+    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
+
+    // light
+    const light = new THREE.PointLight(0xffffff, 4.5, 0, 0);
+    camera.add(light);
+
+    // model
+    new GLTFLoader().load('models/gltf/Nefertiti/Nefertiti.glb', function (gltf) {
+        gltf.scene.traverse(function (child) {
+            if (child.isMesh) {
+                // glTF currently supports only tangent-space normal maps.
+                // this model has been modified to demonstrate the use of an object-space normal map.
+
+                child.material.normalMapType = THREE.ObjectSpaceNormalMap;
+
+                // attribute normals are not required with an object-space normal map. remove them.
+
+                child.geometry.deleteAttribute('normal');
+
+                //
+
+                child.material.side = THREE.DoubleSide;
+
+                child.scale.multiplyScalar(0.5);
+
+                // recenter
+
+                new THREE.Box3().setFromObject(child).getCenter(child.position).multiplyScalar(-1);
+
+                scene.add(child);
+            }
+        });
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_physical_clearcoat.ts b/examples-testing/examples/webgl_materials_physical_clearcoat.ts
new file mode 100644
index 000000000..408fd9921
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_physical_clearcoat.ts
@@ -0,0 +1,208 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
+
+import { FlakesTexture } from 'three/addons/textures/FlakesTexture.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let particleLight;
+let group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.25, 50);
+    camera.position.z = 10;
+
+    scene = new THREE.Scene();
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    new HDRCubeTextureLoader()
+        .setPath('textures/cube/pisaHDR/')
+        .load(['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'], function (texture) {
+            const geometry = new THREE.SphereGeometry(0.8, 64, 32);
+
+            const textureLoader = new THREE.TextureLoader();
+
+            const diffuse = textureLoader.load('textures/carbon/Carbon.png');
+            diffuse.colorSpace = THREE.SRGBColorSpace;
+            diffuse.wrapS = THREE.RepeatWrapping;
+            diffuse.wrapT = THREE.RepeatWrapping;
+            diffuse.repeat.x = 10;
+            diffuse.repeat.y = 10;
+
+            const normalMap = textureLoader.load('textures/carbon/Carbon_Normal.png');
+            normalMap.wrapS = THREE.RepeatWrapping;
+            normalMap.wrapT = THREE.RepeatWrapping;
+            normalMap.repeat.x = 10;
+            normalMap.repeat.y = 10;
+
+            const normalMap2 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
+
+            const normalMap3 = new THREE.CanvasTexture(new FlakesTexture());
+            normalMap3.wrapS = THREE.RepeatWrapping;
+            normalMap3.wrapT = THREE.RepeatWrapping;
+            normalMap3.repeat.x = 10;
+            normalMap3.repeat.y = 6;
+            normalMap3.anisotropy = 16;
+
+            const normalMap4 = textureLoader.load('textures/golfball.jpg');
+
+            const clearcoatNormalMap = textureLoader.load(
+                'textures/pbr/Scratched_gold/Scratched_gold_01_1K_Normal.png',
+            );
+
+            // car paint
+
+            let material = new THREE.MeshPhysicalMaterial({
+                clearcoat: 1.0,
+                clearcoatRoughness: 0.1,
+                metalness: 0.9,
+                roughness: 0.5,
+                color: 0x0000ff,
+                normalMap: normalMap3,
+                normalScale: new THREE.Vector2(0.15, 0.15),
+            });
+
+            let mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = -1;
+            mesh.position.y = 1;
+            group.add(mesh);
+
+            // fibers
+
+            material = new THREE.MeshPhysicalMaterial({
+                roughness: 0.5,
+                clearcoat: 1.0,
+                clearcoatRoughness: 0.1,
+                map: diffuse,
+                normalMap: normalMap,
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = 1;
+            mesh.position.y = 1;
+            group.add(mesh);
+
+            // golf
+
+            material = new THREE.MeshPhysicalMaterial({
+                metalness: 0.0,
+                roughness: 0.1,
+                clearcoat: 1.0,
+                normalMap: normalMap4,
+                clearcoatNormalMap: clearcoatNormalMap,
+
+                // y scale is negated to compensate for normal map handedness.
+                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = -1;
+            mesh.position.y = -1;
+            group.add(mesh);
+
+            // clearcoat + normalmap
+
+            material = new THREE.MeshPhysicalMaterial({
+                clearcoat: 1.0,
+                metalness: 1.0,
+                color: 0xff0000,
+                normalMap: normalMap2,
+                normalScale: new THREE.Vector2(0.15, 0.15),
+                clearcoatNormalMap: clearcoatNormalMap,
+
+                // y scale is negated to compensate for normal map handedness.
+                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = 1;
+            mesh.position.y = -1;
+            group.add(mesh);
+
+            //
+
+            scene.background = texture;
+            scene.environment = texture;
+        });
+
+    // LIGHTS
+
+    particleLight = new THREE.Mesh(
+        new THREE.SphereGeometry(0.05, 8, 8),
+        new THREE.MeshBasicMaterial({ color: 0xffffff }),
+    );
+    scene.add(particleLight);
+
+    particleLight.add(new THREE.PointLight(0xffffff, 30));
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1.25;
+
+    //
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // EVENTS
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 3;
+    controls.maxDistance = 30;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const timer = Date.now() * 0.00025;
+
+    particleLight.position.x = Math.sin(timer * 7) * 3;
+    particleLight.position.y = Math.cos(timer * 5) * 4;
+    particleLight.position.z = Math.cos(timer * 3) * 3;
+
+    for (let i = 0; i < group.children.length; i++) {
+        const child = group.children[i];
+        child.rotation.y += 0.005;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_physical_transmission.ts b/examples-testing/examples/webgl_materials_physical_transmission.ts
new file mode 100644
index 000000000..d45967971
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_physical_transmission.ts
@@ -0,0 +1,182 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+const params = {
+    color: 0xffffff,
+    transmission: 1,
+    opacity: 1,
+    metalness: 0,
+    roughness: 0,
+    ior: 1.5,
+    thickness: 0.01,
+    specularIntensity: 1,
+    specularColor: 0xffffff,
+    envMapIntensity: 1,
+    lightIntensity: 1,
+    exposure: 1,
+};
+
+let camera, scene, renderer;
+
+let mesh;
+
+const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
+    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
+
+    init();
+    render();
+});
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = params.exposure;
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(0, 0, 120);
+
+    //
+
+    scene.background = hdrEquirect;
+
+    //
+
+    const geometry = new THREE.SphereGeometry(20, 64, 32);
+
+    const texture = new THREE.CanvasTexture(generateTexture());
+    texture.magFilter = THREE.NearestFilter;
+    texture.wrapT = THREE.RepeatWrapping;
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.repeat.set(1, 3.5);
+
+    const material = new THREE.MeshPhysicalMaterial({
+        color: params.color,
+        metalness: params.metalness,
+        roughness: params.roughness,
+        ior: params.ior,
+        alphaMap: texture,
+        envMap: hdrEquirect,
+        envMapIntensity: params.envMapIntensity,
+        transmission: params.transmission, // use material.transmission for glass materials
+        specularIntensity: params.specularIntensity,
+        specularColor: params.specularColor,
+        opacity: params.opacity,
+        side: THREE.DoubleSide,
+        transparent: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 10;
+    controls.maxDistance = 150;
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    gui.addColor(params, 'color').onChange(function () {
+        material.color.set(params.color);
+        render();
+    });
+
+    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
+        material.transmission = params.transmission;
+        render();
+    });
+
+    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
+        material.opacity = params.opacity;
+        render();
+    });
+
+    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
+        material.metalness = params.metalness;
+        render();
+    });
+
+    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
+        material.roughness = params.roughness;
+        render();
+    });
+
+    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
+        material.ior = params.ior;
+        render();
+    });
+
+    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
+        material.thickness = params.thickness;
+        render();
+    });
+
+    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
+        material.specularIntensity = params.specularIntensity;
+        render();
+    });
+
+    gui.addColor(params, 'specularColor').onChange(function () {
+        material.specularColor.set(params.specularColor);
+        render();
+    });
+
+    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
+        .name('envMap intensity')
+        .onChange(function () {
+            material.envMapIntensity = params.envMapIntensity;
+            render();
+        });
+
+    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
+        renderer.toneMappingExposure = params.exposure;
+        render();
+    });
+
+    gui.open();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+
+    render();
+}
+
+//
+
+function generateTexture() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 2;
+    canvas.height = 2;
+
+    const context = canvas.getContext('2d');
+    context.fillStyle = 'white';
+    context.fillRect(0, 1, 2, 1);
+
+    return canvas;
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts b/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
new file mode 100644
index 000000000..d81f59c37
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
@@ -0,0 +1,192 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+const params = {
+    color: 0xffffff,
+    transmission: 1,
+    opacity: 1,
+    metalness: 0,
+    roughness: 0,
+    ior: 1.5,
+    thickness: 0.01,
+    attenuationColor: 0xffffff,
+    attenuationDistance: 1,
+    specularIntensity: 1,
+    specularColor: 0xffffff,
+    envMapIntensity: 1,
+    lightIntensity: 1,
+    exposure: 1,
+};
+
+let camera, scene, renderer;
+
+let mesh, material;
+
+const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
+    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
+
+    new GLTFLoader().setPath('models/gltf/').load('DragonAttenuation.glb', function (gltf) {
+        gltf.scene.traverse(function (child) {
+            if (child.isMesh && child.material.isMeshPhysicalMaterial) {
+                mesh = child;
+                material = mesh.material;
+
+                const color = new THREE.Color();
+
+                params.color = color.copy(mesh.material.color).getHex();
+                params.roughness = mesh.material.roughness;
+                params.metalness = mesh.material.metalness;
+
+                params.ior = mesh.material.ior;
+                params.specularIntensity = mesh.material.specularIntensity;
+
+                params.transmission = mesh.material.transmission;
+                params.thickness = mesh.material.thickness;
+                params.attenuationColor = color.copy(mesh.material.attenuationColor).getHex();
+                params.attenuationDistance = mesh.material.attenuationDistance;
+            }
+        });
+
+        init();
+
+        scene.add(gltf.scene);
+
+        scene.environment = hdrEquirect;
+        //scene.background = hdrEquirect;
+
+        render();
+    });
+});
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = params.exposure;
+
+    // accommodate CSS table
+    renderer.domElement.style.position = 'absolute';
+    renderer.domElement.style.top = 0;
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(-5, 0.5, 0);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 5;
+    controls.maxDistance = 20;
+    controls.target.y = 0.5;
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    gui.addColor(params, 'color').onChange(function () {
+        material.color.set(params.color);
+        render();
+    });
+
+    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
+        material.transmission = params.transmission;
+        render();
+    });
+
+    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
+        material.opacity = params.opacity;
+        const transparent = params.opacity < 1;
+
+        if (transparent !== material.transparent) {
+            material.transparent = transparent;
+            material.needsUpdate = true;
+        }
+
+        render();
+    });
+
+    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
+        material.metalness = params.metalness;
+        render();
+    });
+
+    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
+        material.roughness = params.roughness;
+        render();
+    });
+
+    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
+        material.ior = params.ior;
+        render();
+    });
+
+    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
+        material.thickness = params.thickness;
+        render();
+    });
+
+    gui.addColor(params, 'attenuationColor')
+        .name('attenuation color')
+        .onChange(function () {
+            material.attenuationColor.set(params.attenuationColor);
+            render();
+        });
+
+    gui.add(params, 'attenuationDistance', 0, 1, 0.01).onChange(function () {
+        material.attenuationDistance = params.attenuationDistance;
+        render();
+    });
+
+    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
+        material.specularIntensity = params.specularIntensity;
+        render();
+    });
+
+    gui.addColor(params, 'specularColor').onChange(function () {
+        material.specularColor.set(params.specularColor);
+        render();
+    });
+
+    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
+        .name('envMap intensity')
+        .onChange(function () {
+            material.envMapIntensity = params.envMapIntensity;
+            render();
+        });
+
+    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
+        renderer.toneMappingExposure = params.exposure;
+        render();
+    });
+
+    gui.open();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_texture_anisotropy.ts b/examples-testing/examples/webgl_materials_texture_anisotropy.ts
new file mode 100644
index 000000000..1e030d64d
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_anisotropy.ts
@@ -0,0 +1,143 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+const SCREEN_WIDTH = window.innerWidth;
+const SCREEN_HEIGHT = window.innerHeight;
+
+let container, stats;
+
+let camera, scene1, scene2, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+
+    //
+
+    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 25000);
+    camera.position.z = 1500;
+
+    scene1 = new THREE.Scene();
+    scene1.background = new THREE.Color(0xf2f7ff);
+    scene1.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
+
+    scene2 = new THREE.Scene();
+    scene2.background = new THREE.Color(0xf2f7ff);
+    scene2.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
+
+    scene1.add(new THREE.AmbientLight(0xeef0ff, 3));
+    scene2.add(new THREE.AmbientLight(0xeef0ff, 3));
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 6);
+    light1.position.set(1, 1, 1);
+    scene1.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 6);
+    light2.position.set(1, 1, 1);
+    scene2.add(light2);
+
+    // GROUND
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
+
+    const texture1 = textureLoader.load('textures/crate.gif');
+    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
+
+    texture1.colorSpace = THREE.SRGBColorSpace;
+    texture1.anisotropy = maxAnisotropy;
+    texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
+    texture1.repeat.set(512, 512);
+
+    const texture2 = textureLoader.load('textures/crate.gif');
+    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
+
+    texture2.colorSpace = THREE.SRGBColorSpace;
+    texture2.anisotropy = 1;
+    texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping;
+    texture2.repeat.set(512, 512);
+
+    if (maxAnisotropy > 0) {
+        document.getElementById('val_left').innerHTML = texture1.anisotropy;
+        document.getElementById('val_right').innerHTML = texture2.anisotropy;
+    } else {
+        document.getElementById('val_left').innerHTML = 'not supported';
+        document.getElementById('val_right').innerHTML = 'not supported';
+    }
+
+    //
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+
+    const mesh1 = new THREE.Mesh(geometry, material1);
+    mesh1.rotation.x = -Math.PI / 2;
+    mesh1.scale.set(1000, 1000, 1000);
+
+    const mesh2 = new THREE.Mesh(geometry, material2);
+    mesh2.rotation.x = -Math.PI / 2;
+    mesh2.scale.set(1000, 1000, 1000);
+
+    scene1.add(mesh1);
+    scene2.add(mesh2);
+
+    // RENDERER
+
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    renderer.domElement.style.position = 'relative';
+    container.appendChild(renderer.domElement);
+
+    // STATS1
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y = THREE.MathUtils.clamp(
+        camera.position.y + (-(mouseY - 200) - camera.position.y) * 0.05,
+        50,
+        1000,
+    );
+
+    camera.lookAt(scene1.position);
+
+    renderer.clear();
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene1, camera);
+
+    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene2, camera);
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgl_materials_texture_canvas.ts b/examples-testing/examples/webgl_materials_texture_canvas.ts
new file mode 100644
index 000000000..d23c68436
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_canvas.ts
@@ -0,0 +1,92 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, mesh, material;
+const drawStartPos = new THREE.Vector2();
+
+init();
+setupCanvasDrawing();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    material = new THREE.MeshBasicMaterial();
+
+    mesh = new THREE.Mesh(new THREE.BoxGeometry(200, 200, 200), material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+// Sets up the drawing canvas and adds it as the material map
+
+function setupCanvasDrawing() {
+    // get canvas and context
+
+    const drawingCanvas = document.getElementById('drawing-canvas');
+    const drawingContext = drawingCanvas.getContext('2d');
+
+    // draw white background
+
+    drawingContext.fillStyle = '#FFFFFF';
+    drawingContext.fillRect(0, 0, 128, 128);
+
+    // set canvas as material.map (this could be done to any map, bump, displacement etc.)
+
+    material.map = new THREE.CanvasTexture(drawingCanvas);
+
+    // set the variable to keep track of when to draw
+
+    let paint = false;
+
+    // add canvas event listeners
+    drawingCanvas.addEventListener('pointerdown', function (e) {
+        paint = true;
+        drawStartPos.set(e.offsetX, e.offsetY);
+    });
+
+    drawingCanvas.addEventListener('pointermove', function (e) {
+        if (paint) draw(drawingContext, e.offsetX, e.offsetY);
+    });
+
+    drawingCanvas.addEventListener('pointerup', function () {
+        paint = false;
+    });
+
+    drawingCanvas.addEventListener('pointerleave', function () {
+        paint = false;
+    });
+}
+
+function draw(drawContext, x, y) {
+    drawContext.moveTo(drawStartPos.x, drawStartPos.y);
+    drawContext.strokeStyle = '#000000';
+    drawContext.lineTo(x, y);
+    drawContext.stroke();
+    // reset drawing start position to current position.
+    drawStartPos.set(x, y);
+    // need to flag the map as needing updating.
+    material.map.needsUpdate = true;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.rotation.x += 0.01;
+    mesh.rotation.y += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_texture_filters.ts b/examples-testing/examples/webgl_materials_texture_filters.ts
new file mode 100644
index 000000000..178c2ce49
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_filters.ts
@@ -0,0 +1,164 @@
+import * as THREE from 'three';
+
+const SCREEN_WIDTH = window.innerWidth;
+const SCREEN_HEIGHT = window.innerHeight;
+
+let container;
+
+let camera, scene, scene2, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 5000);
+    camera.position.z = 1500;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x000000);
+    scene.fog = new THREE.Fog(0x000000, 1500, 4000);
+
+    scene2 = new THREE.Scene();
+    scene2.background = new THREE.Color(0x000000);
+    scene2.fog = new THREE.Fog(0x000000, 1500, 4000);
+
+    // GROUND
+
+    const imageCanvas = document.createElement('canvas');
+    const context = imageCanvas.getContext('2d');
+
+    imageCanvas.width = imageCanvas.height = 128;
+
+    context.fillStyle = '#444';
+    context.fillRect(0, 0, 128, 128);
+
+    context.fillStyle = '#fff';
+    context.fillRect(0, 0, 64, 64);
+    context.fillRect(64, 64, 64, 64);
+
+    const textureCanvas = new THREE.CanvasTexture(imageCanvas);
+    textureCanvas.colorSpace = THREE.SRGBColorSpace;
+    textureCanvas.repeat.set(1000, 1000);
+    textureCanvas.wrapS = THREE.RepeatWrapping;
+    textureCanvas.wrapT = THREE.RepeatWrapping;
+
+    const textureCanvas2 = textureCanvas.clone();
+    textureCanvas2.magFilter = THREE.NearestFilter;
+    textureCanvas2.minFilter = THREE.NearestFilter;
+
+    const materialCanvas = new THREE.MeshBasicMaterial({ map: textureCanvas });
+    const materialCanvas2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: textureCanvas2 });
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+
+    const meshCanvas = new THREE.Mesh(geometry, materialCanvas);
+    meshCanvas.rotation.x = -Math.PI / 2;
+    meshCanvas.scale.set(1000, 1000, 1000);
+
+    const meshCanvas2 = new THREE.Mesh(geometry, materialCanvas2);
+    meshCanvas2.rotation.x = -Math.PI / 2;
+    meshCanvas2.scale.set(1000, 1000, 1000);
+
+    // PAINTING
+
+    const callbackPainting = function () {
+        const image = texturePainting.image;
+
+        texturePainting2.image = image;
+        texturePainting2.needsUpdate = true;
+
+        scene.add(meshCanvas);
+        scene2.add(meshCanvas2);
+
+        const geometry = new THREE.PlaneGeometry(100, 100);
+        const mesh = new THREE.Mesh(geometry, materialPainting);
+        const mesh2 = new THREE.Mesh(geometry, materialPainting2);
+
+        addPainting(scene, mesh);
+        addPainting(scene2, mesh2);
+
+        function addPainting(zscene, zmesh) {
+            zmesh.scale.x = image.width / 100;
+            zmesh.scale.y = image.height / 100;
+
+            zscene.add(zmesh);
+
+            const meshFrame = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000 }));
+            meshFrame.position.z = -10.0;
+            meshFrame.scale.x = (1.1 * image.width) / 100;
+            meshFrame.scale.y = (1.1 * image.height) / 100;
+            zscene.add(meshFrame);
+
+            const meshShadow = new THREE.Mesh(
+                geometry,
+                new THREE.MeshBasicMaterial({ color: 0x000000, opacity: 0.75, transparent: true }),
+            );
+            meshShadow.position.y = (-1.1 * image.height) / 2;
+            meshShadow.position.z = (-1.1 * image.height) / 2;
+            meshShadow.rotation.x = -Math.PI / 2;
+            meshShadow.scale.x = (1.1 * image.width) / 100;
+            meshShadow.scale.y = (1.1 * image.height) / 100;
+            zscene.add(meshShadow);
+
+            const floorHeight = (-1.117 * image.height) / 2;
+            meshCanvas.position.y = meshCanvas2.position.y = floorHeight;
+        }
+    };
+
+    const texturePainting = new THREE.TextureLoader().load(
+        'textures/758px-Canestra_di_frutta_(Caravaggio).jpg',
+        callbackPainting,
+    );
+    const texturePainting2 = new THREE.Texture();
+
+    const materialPainting = new THREE.MeshBasicMaterial({ color: 0xffffff, map: texturePainting });
+    const materialPainting2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: texturePainting2 });
+
+    texturePainting.colorSpace = THREE.SRGBColorSpace;
+    texturePainting2.colorSpace = THREE.SRGBColorSpace;
+    texturePainting2.minFilter = texturePainting2.magFilter = THREE.NearestFilter;
+    texturePainting.minFilter = texturePainting.magFilter = THREE.LinearFilter;
+    texturePainting.mapping = THREE.UVMapping;
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    renderer.domElement.style.position = 'relative';
+    container.appendChild(renderer.domElement);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+function animate() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-(mouseY - 200) - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    renderer.clear();
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene, camera);
+
+    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene2, camera);
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgl_materials_texture_manualmipmap.ts b/examples-testing/examples/webgl_materials_texture_manualmipmap.ts
new file mode 100644
index 000000000..24bd4eb9f
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_manualmipmap.ts
@@ -0,0 +1,175 @@
+import * as THREE from 'three';
+
+const SCREEN_WIDTH = window.innerWidth;
+const SCREEN_HEIGHT = window.innerHeight;
+
+let container;
+
+let camera, scene1, scene2, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 5000);
+    camera.position.z = 1500;
+
+    scene1 = new THREE.Scene();
+    scene1.background = new THREE.Color(0x000000);
+    scene1.fog = new THREE.Fog(0x000000, 1500, 4000);
+
+    scene2 = new THREE.Scene();
+    scene2.background = new THREE.Color(0x000000);
+    scene2.fog = new THREE.Fog(0x000000, 1500, 4000);
+
+    // GROUND
+
+    function mipmap(size, color) {
+        const imageCanvas = document.createElement('canvas');
+        const context = imageCanvas.getContext('2d');
+
+        imageCanvas.width = imageCanvas.height = size;
+
+        context.fillStyle = '#444';
+        context.fillRect(0, 0, size, size);
+
+        context.fillStyle = color;
+        context.fillRect(0, 0, size / 2, size / 2);
+        context.fillRect(size / 2, size / 2, size / 2, size / 2);
+        return imageCanvas;
+    }
+
+    const canvas = mipmap(128, '#f00');
+    const textureCanvas1 = new THREE.CanvasTexture(canvas);
+    textureCanvas1.mipmaps[0] = canvas;
+    textureCanvas1.mipmaps[1] = mipmap(64, '#0f0');
+    textureCanvas1.mipmaps[2] = mipmap(32, '#00f');
+    textureCanvas1.mipmaps[3] = mipmap(16, '#400');
+    textureCanvas1.mipmaps[4] = mipmap(8, '#040');
+    textureCanvas1.mipmaps[5] = mipmap(4, '#004');
+    textureCanvas1.mipmaps[6] = mipmap(2, '#044');
+    textureCanvas1.mipmaps[7] = mipmap(1, '#404');
+    textureCanvas1.colorSpace = THREE.SRGBColorSpace;
+    textureCanvas1.repeat.set(1000, 1000);
+    textureCanvas1.wrapS = THREE.RepeatWrapping;
+    textureCanvas1.wrapT = THREE.RepeatWrapping;
+
+    const textureCanvas2 = textureCanvas1.clone();
+    textureCanvas2.magFilter = THREE.NearestFilter;
+    textureCanvas2.minFilter = THREE.NearestMipmapNearestFilter;
+
+    const materialCanvas1 = new THREE.MeshBasicMaterial({ map: textureCanvas1 });
+    const materialCanvas2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: textureCanvas2 });
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+
+    const meshCanvas1 = new THREE.Mesh(geometry, materialCanvas1);
+    meshCanvas1.rotation.x = -Math.PI / 2;
+    meshCanvas1.scale.set(1000, 1000, 1000);
+
+    const meshCanvas2 = new THREE.Mesh(geometry, materialCanvas2);
+    meshCanvas2.rotation.x = -Math.PI / 2;
+    meshCanvas2.scale.set(1000, 1000, 1000);
+
+    // PAINTING
+
+    const callbackPainting = function () {
+        const image = texturePainting1.image;
+
+        texturePainting2.image = image;
+        texturePainting2.needsUpdate = true;
+
+        scene1.add(meshCanvas1);
+        scene2.add(meshCanvas2);
+
+        const geometry = new THREE.PlaneGeometry(100, 100);
+        const mesh1 = new THREE.Mesh(geometry, materialPainting1);
+        const mesh2 = new THREE.Mesh(geometry, materialPainting2);
+
+        addPainting(scene1, mesh1);
+        addPainting(scene2, mesh2);
+
+        function addPainting(zscene, zmesh) {
+            zmesh.scale.x = image.width / 100;
+            zmesh.scale.y = image.height / 100;
+
+            zscene.add(zmesh);
+
+            const meshFrame = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000 }));
+            meshFrame.position.z = -10.0;
+            meshFrame.scale.x = (1.1 * image.width) / 100;
+            meshFrame.scale.y = (1.1 * image.height) / 100;
+            zscene.add(meshFrame);
+
+            const meshShadow = new THREE.Mesh(
+                geometry,
+                new THREE.MeshBasicMaterial({ color: 0x000000, opacity: 0.75, transparent: true }),
+            );
+            meshShadow.position.y = (-1.1 * image.height) / 2;
+            meshShadow.position.z = (-1.1 * image.height) / 2;
+            meshShadow.rotation.x = -Math.PI / 2;
+            meshShadow.scale.x = (1.1 * image.width) / 100;
+            meshShadow.scale.y = (1.1 * image.height) / 100;
+            zscene.add(meshShadow);
+
+            const floorHeight = (-1.117 * image.height) / 2;
+            meshCanvas1.position.y = meshCanvas2.position.y = floorHeight;
+        }
+    };
+
+    const texturePainting1 = new THREE.TextureLoader().load(
+        'textures/758px-Canestra_di_frutta_(Caravaggio).jpg',
+        callbackPainting,
+    );
+    const texturePainting2 = new THREE.Texture();
+    const materialPainting1 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: texturePainting1 });
+    const materialPainting2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: texturePainting2 });
+
+    texturePainting1.colorSpace = THREE.SRGBColorSpace;
+    texturePainting2.colorSpace = THREE.SRGBColorSpace;
+    texturePainting2.minFilter = texturePainting2.magFilter = THREE.NearestFilter;
+    texturePainting1.minFilter = texturePainting1.magFilter = THREE.LinearFilter;
+    texturePainting1.mapping = THREE.UVMapping;
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    renderer.domElement.style.position = 'relative';
+    container.appendChild(renderer.domElement);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+function animate() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-(mouseY - 200) - camera.position.y) * 0.05;
+
+    camera.lookAt(scene1.position);
+
+    renderer.clear();
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene1, camera);
+
+    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene2, camera);
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgl_materials_texture_partialupdate.ts b/examples-testing/examples/webgl_materials_texture_partialupdate.ts
new file mode 100644
index 000000000..5adfc8e69
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_partialupdate.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, clock, dataTexture, diffuseMap;
+
+let last = 0;
+const position = new THREE.Vector2();
+const color = new THREE.Color();
+
+init();
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    const loader = new THREE.TextureLoader();
+    diffuseMap = await loader.loadAsync('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
+    diffuseMap.colorSpace = THREE.SRGBColorSpace;
+    diffuseMap.minFilter = THREE.LinearFilter;
+    diffuseMap.generateMipmaps = false;
+
+    const geometry = new THREE.PlaneGeometry(2, 2);
+    const material = new THREE.MeshBasicMaterial({ map: diffuseMap });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const width = 32;
+    const height = 32;
+
+    const data = new Uint8Array(width * height * 4);
+    dataTexture = new THREE.DataTexture(data, width, height);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const elapsedTime = clock.getElapsedTime();
+
+    if (elapsedTime - last > 0.1) {
+        last = elapsedTime;
+
+        position.x = 32 * THREE.MathUtils.randInt(1, 16) - 32;
+        position.y = 32 * THREE.MathUtils.randInt(1, 16) - 32;
+
+        // generate new color data
+
+        updateDataTexture(dataTexture);
+
+        // perform copy from src to dest texture to a random position
+
+        renderer.copyTextureToTexture(dataTexture, diffuseMap, null, position);
+    }
+
+    renderer.render(scene, camera);
+}
+
+function updateDataTexture(texture) {
+    const size = texture.image.width * texture.image.height;
+    const data = texture.image.data;
+
+    // generate a random color and update texture data
+
+    color.setHex(Math.random() * 0xffffff);
+
+    const r = Math.floor(color.r * 255);
+    const g = Math.floor(color.g * 255);
+    const b = Math.floor(color.b * 255);
+
+    for (let i = 0; i < size; i++) {
+        const stride = i * 4;
+
+        data[stride] = r;
+        data[stride + 1] = g;
+        data[stride + 2] = b;
+        data[stride + 3] = 1;
+    }
+}
diff --git a/examples-testing/examples/webgl_materials_texture_rotation.ts b/examples-testing/examples/webgl_materials_texture_rotation.ts
new file mode 100644
index 000000000..2666d09d6
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_texture_rotation.ts
@@ -0,0 +1,113 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let mesh, renderer, scene, camera;
+
+let gui;
+
+const API = {
+    offsetX: 0,
+    offsetY: 0,
+    repeatX: 0.25,
+    repeatY: 0.25,
+    rotation: Math.PI / 4, // positive is counterclockwise
+    centerX: 0.5,
+    centerY: 0.5,
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(10, 15, 25);
+    scene.add(camera);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.minDistance = 20;
+    controls.maxDistance = 50;
+    controls.maxPolarAngle = Math.PI / 2;
+
+    const geometry = new THREE.BoxGeometry(10, 10, 10);
+
+    new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg', function (texture) {
+        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+        texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
+        texture.colorSpace = THREE.SRGBColorSpace;
+
+        //texture.matrixAutoUpdate = false; // default true; set to false to update texture.matrix manually
+
+        const material = new THREE.MeshBasicMaterial({ map: texture });
+
+        mesh = new THREE.Mesh(geometry, material);
+        scene.add(mesh);
+
+        updateUvTransform();
+
+        initGui();
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function updateUvTransform() {
+    const texture = mesh.material.map;
+
+    if (texture.matrixAutoUpdate === true) {
+        texture.offset.set(API.offsetX, API.offsetY);
+        texture.repeat.set(API.repeatX, API.repeatY);
+        texture.center.set(API.centerX, API.centerY);
+        texture.rotation = API.rotation; // rotation is around center
+    } else {
+        // setting the matrix uv transform directly
+        //texture.matrix.setUvTransform( API.offsetX, API.offsetY, API.repeatX, API.repeatY, API.rotation, API.centerX, API.centerY );
+
+        // another way...
+        texture.matrix
+            .identity()
+            .translate(-API.centerX, -API.centerY)
+            .rotate(API.rotation) // I don't understand how rotation can preceed scale, but it seems to be required...
+            .scale(API.repeatX, API.repeatY)
+            .translate(API.centerX, API.centerY)
+            .translate(API.offsetX, API.offsetY);
+    }
+
+    render();
+}
+
+function initGui() {
+    gui = new GUI();
+
+    gui.add(API, 'offsetX', 0.0, 1.0).name('offset.x').onChange(updateUvTransform);
+    gui.add(API, 'offsetY', 0.0, 1.0).name('offset.y').onChange(updateUvTransform);
+    gui.add(API, 'repeatX', 0.25, 2.0).name('repeat.x').onChange(updateUvTransform);
+    gui.add(API, 'repeatY', 0.25, 2.0).name('repeat.y').onChange(updateUvTransform);
+    gui.add(API, 'rotation', -2.0, 2.0).name('rotation').onChange(updateUvTransform);
+    gui.add(API, 'centerX', 0.0, 1.0).name('center.x').onChange(updateUvTransform);
+    gui.add(API, 'centerY', 0.0, 1.0).name('center.y').onChange(updateUvTransform);
+}
diff --git a/examples-testing/examples/webgl_materials_toon.ts b/examples-testing/examples/webgl_materials_toon.ts
new file mode 100644
index 000000000..03db286ad
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_toon.ts
@@ -0,0 +1,152 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OutlineEffect } from 'three/addons/effects/OutlineEffect.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+let container, stats;
+
+let camera, scene, renderer, effect;
+let particleLight;
+
+const loader = new FontLoader();
+loader.load('fonts/gentilis_regular.typeface.json', function (font) {
+    init(font);
+});
+
+function init(font) {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2500);
+    camera.position.set(0.0, 400, 400 * 3.5);
+
+    //
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x444488);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // Materials
+
+    const cubeWidth = 400;
+    const numberOfSphersPerSide = 5;
+    const sphereRadius = (cubeWidth / numberOfSphersPerSide) * 0.8 * 0.5;
+    const stepSize = 1.0 / numberOfSphersPerSide;
+
+    const geometry = new THREE.SphereGeometry(sphereRadius, 32, 16);
+
+    for (let alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex++) {
+        const colors = new Uint8Array(alphaIndex + 2);
+
+        for (let c = 0; c <= colors.length; c++) {
+            colors[c] = (c / colors.length) * 256;
+        }
+
+        const gradientMap = new THREE.DataTexture(colors, colors.length, 1, THREE.RedFormat);
+        gradientMap.needsUpdate = true;
+
+        for (let beta = 0; beta <= 1.0; beta += stepSize) {
+            for (let gamma = 0; gamma <= 1.0; gamma += stepSize) {
+                // basic monochromatic energy preservation
+                const diffuseColor = new THREE.Color()
+                    .setHSL(alpha, 0.5, gamma * 0.5 + 0.1)
+                    .multiplyScalar(1 - beta * 0.2);
+
+                const material = new THREE.MeshToonMaterial({
+                    color: diffuseColor,
+                    gradientMap: gradientMap,
+                });
+
+                const mesh = new THREE.Mesh(geometry, material);
+
+                mesh.position.x = alpha * 400 - 200;
+                mesh.position.y = beta * 400 - 200;
+                mesh.position.z = gamma * 400 - 200;
+
+                scene.add(mesh);
+            }
+        }
+    }
+
+    function addLabel(name, location) {
+        const textGeo = new TextGeometry(name, {
+            font: font,
+
+            size: 20,
+            depth: 1,
+            curveSegments: 1,
+        });
+
+        const textMaterial = new THREE.MeshBasicMaterial();
+        const textMesh = new THREE.Mesh(textGeo, textMaterial);
+        textMesh.position.copy(location);
+        scene.add(textMesh);
+    }
+
+    addLabel('-gradientMap', new THREE.Vector3(-350, 0, 0));
+    addLabel('+gradientMap', new THREE.Vector3(350, 0, 0));
+
+    addLabel('-diffuse', new THREE.Vector3(0, 0, -300));
+    addLabel('+diffuse', new THREE.Vector3(0, 0, 300));
+
+    particleLight = new THREE.Mesh(new THREE.SphereGeometry(4, 8, 8), new THREE.MeshBasicMaterial({ color: 0xffffff }));
+    scene.add(particleLight);
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0xc1c1c1, 3));
+
+    const pointLight = new THREE.PointLight(0xffffff, 2, 800, 0);
+    particleLight.add(pointLight);
+
+    //
+
+    effect = new OutlineEffect(renderer);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 200;
+    controls.maxDistance = 2000;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    const timer = Date.now() * 0.00025;
+
+    particleLight.position.x = Math.sin(timer * 7) * 300;
+    particleLight.position.y = Math.cos(timer * 5) * 400;
+    particleLight.position.z = Math.cos(timer * 3) * 300;
+
+    effect.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_video.ts b/examples-testing/examples/webgl_materials_video.ts
new file mode 100644
index 000000000..4f0d26a18
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_video.ts
@@ -0,0 +1,208 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let container;
+
+let camera, scene, renderer;
+
+let video, texture, material, mesh;
+
+let composer;
+
+let mouseX = 0;
+let mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+let cube_count;
+
+const meshes = [],
+    materials = [],
+    xgrid = 20,
+    ygrid = 10;
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', function () {
+    init();
+});
+
+function init() {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0.5, 1, 1).normalize();
+    scene.add(light);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    video = document.getElementById('video');
+    video.play();
+    video.addEventListener('play', function () {
+        this.currentTime = 3;
+    });
+
+    texture = new THREE.VideoTexture(video);
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    //
+
+    let i, j, ox, oy, geometry;
+
+    const ux = 1 / xgrid;
+    const uy = 1 / ygrid;
+
+    const xsize = 480 / xgrid;
+    const ysize = 204 / ygrid;
+
+    const parameters = { color: 0xffffff, map: texture };
+
+    cube_count = 0;
+
+    for (i = 0; i < xgrid; i++) {
+        for (j = 0; j < ygrid; j++) {
+            ox = i;
+            oy = j;
+
+            geometry = new THREE.BoxGeometry(xsize, ysize, xsize);
+
+            change_uvs(geometry, ux, uy, ox, oy);
+
+            materials[cube_count] = new THREE.MeshLambertMaterial(parameters);
+
+            material = materials[cube_count];
+
+            material.hue = i / xgrid;
+            material.saturation = 1 - j / ygrid;
+
+            material.color.setHSL(material.hue, material.saturation, 0.5);
+
+            mesh = new THREE.Mesh(geometry, material);
+
+            mesh.position.x = (i - xgrid / 2) * xsize;
+            mesh.position.y = (j - ygrid / 2) * ysize;
+            mesh.position.z = 0;
+
+            mesh.scale.x = mesh.scale.y = mesh.scale.z = 1;
+
+            scene.add(mesh);
+
+            mesh.dx = 0.001 * (0.5 - Math.random());
+            mesh.dy = 0.001 * (0.5 - Math.random());
+
+            meshes[cube_count] = mesh;
+
+            cube_count += 1;
+        }
+    }
+
+    renderer.autoClear = false;
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    // postprocessing
+
+    const renderPass = new RenderPass(scene, camera);
+    const bloomPass = new BloomPass(1.3);
+    const outputPass = new OutputPass();
+
+    composer = new EffectComposer(renderer);
+
+    composer.addPass(renderPass);
+    composer.addPass(bloomPass);
+    composer.addPass(outputPass);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function change_uvs(geometry, unitx, unity, offsetx, offsety) {
+    const uvs = geometry.attributes.uv.array;
+
+    for (let i = 0; i < uvs.length; i += 2) {
+        uvs[i] = (uvs[i] + offsetx) * unitx;
+        uvs[i + 1] = (uvs[i + 1] + offsety) * unity;
+    }
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = (event.clientY - windowHalfY) * 0.3;
+}
+
+//
+
+let h,
+    counter = 1;
+
+function animate() {
+    const time = Date.now() * 0.00005;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0; i < cube_count; i++) {
+        material = materials[i];
+
+        h = ((360 * (material.hue + time)) % 360) / 360;
+        material.color.setHSL(h, material.saturation, 0.5);
+    }
+
+    if (counter % 1000 > 200) {
+        for (let i = 0; i < cube_count; i++) {
+            mesh = meshes[i];
+
+            mesh.rotation.x += 10 * mesh.dx;
+            mesh.rotation.y += 10 * mesh.dy;
+
+            mesh.position.x -= 150 * mesh.dx;
+            mesh.position.y += 150 * mesh.dy;
+            mesh.position.z += 300 * mesh.dx;
+        }
+    }
+
+    if (counter % 1000 === 0) {
+        for (let i = 0; i < cube_count; i++) {
+            mesh = meshes[i];
+
+            mesh.dx *= -1;
+            mesh.dy *= -1;
+        }
+    }
+
+    counter++;
+
+    renderer.clear();
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_materials_video_webcam.ts b/examples-testing/examples/webgl_materials_video_webcam.ts
new file mode 100644
index 000000000..cf6f8d50c
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_video_webcam.ts
@@ -0,0 +1,79 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, video;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 0.01;
+
+    scene = new THREE.Scene();
+
+    video = document.getElementById('video');
+
+    const texture = new THREE.VideoTexture(video);
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const geometry = new THREE.PlaneGeometry(16, 9);
+    geometry.scale(0.5, 0.5, 0.5);
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    const count = 128;
+    const radius = 32;
+
+    for (let i = 1, l = count; i <= l; i++) {
+        const phi = Math.acos(-1 + (2 * i) / l);
+        const theta = Math.sqrt(l * Math.PI) * phi;
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.setFromSphericalCoords(radius, phi, theta);
+        mesh.lookAt(camera.position);
+        scene.add(mesh);
+    }
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enablePan = false;
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+        const constraints = { video: { width: 1280, height: 720, facingMode: 'user' } };
+
+        navigator.mediaDevices
+            .getUserMedia(constraints)
+            .then(function (stream) {
+                // apply the stream to the video element used in the texture
+
+                video.srcObject = stream;
+                video.play();
+            })
+            .catch(function (error) {
+                console.error('Unable to access the camera/webcam.', error);
+            });
+    } else {
+        console.error('MediaDevices interface not available.');
+    }
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_materials_wireframe.ts b/examples-testing/examples/webgl_materials_wireframe.ts
new file mode 100644
index 000000000..8adbd71d6
--- /dev/null
+++ b/examples-testing/examples/webgl_materials_wireframe.ts
@@ -0,0 +1,107 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const API = {
+    thickness: 1,
+};
+
+let renderer, scene, camera, mesh2;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.z = 200;
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.enablePan = false;
+    controls.enableZoom = false;
+
+    new THREE.BufferGeometryLoader().load('models/json/WaltHeadLo_buffergeometry.json', function (geometry) {
+        geometry.deleteAttribute('normal');
+        geometry.deleteAttribute('uv');
+
+        setupAttributes(geometry);
+
+        // left
+
+        const material1 = new THREE.MeshBasicMaterial({
+            color: 0xe0e0ff,
+            wireframe: true,
+        });
+
+        const mesh1 = new THREE.Mesh(geometry, material1);
+        mesh1.position.set(-40, 0, 0);
+
+        scene.add(mesh1);
+
+        // right
+
+        const material2 = new THREE.ShaderMaterial({
+            uniforms: { thickness: { value: API.thickness } },
+            vertexShader: document.getElementById('vertexShader').textContent,
+            fragmentShader: document.getElementById('fragmentShader').textContent,
+            side: THREE.DoubleSide,
+            alphaToCoverage: true, // only works when WebGLRenderer's "antialias" is set to "true"
+        });
+
+        mesh2 = new THREE.Mesh(geometry, material2);
+        mesh2.position.set(40, 0, 0);
+
+        scene.add(mesh2);
+
+        //
+
+        render();
+    });
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(API, 'thickness', 0, 4).onChange(function () {
+        mesh2.material.uniforms.thickness.value = API.thickness;
+        render();
+    });
+
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function setupAttributes(geometry) {
+    const vectors = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 1)];
+
+    const position = geometry.attributes.position;
+    const centers = new Float32Array(position.count * 3);
+
+    for (let i = 0, l = position.count; i < l; i++) {
+        vectors[i % 3].toArray(centers, i * 3);
+    }
+
+    geometry.setAttribute('center', new THREE.BufferAttribute(centers, 3));
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_math_obb.ts b/examples-testing/examples/webgl_math_obb.ts
new file mode 100644
index 000000000..48480d10b
--- /dev/null
+++ b/examples-testing/examples/webgl_math_obb.ts
@@ -0,0 +1,189 @@
+import * as THREE from 'three';
+
+import { OBB } from 'three/addons/math/OBB.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, clock, controls, stats, raycaster, hitbox;
+
+const objects = [],
+    mouse = new THREE.Vector2();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 75);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    clock = new THREE.Clock();
+
+    raycaster = new THREE.Raycaster();
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x222222, 4);
+    hemiLight.position.set(1, 1, 1);
+    scene.add(hemiLight);
+
+    const size = new THREE.Vector3(10, 5, 6);
+    const geometry = new THREE.BoxGeometry(size.x, size.y, size.z);
+
+    // setup OBB on geometry level (doing this manually for now)
+
+    geometry.userData.obb = new OBB();
+    geometry.userData.obb.halfSize.copy(size).multiplyScalar(0.5);
+
+    for (let i = 0; i < 100; i++) {
+        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: 0x00ff00 }));
+        object.matrixAutoUpdate = false;
+
+        object.position.x = Math.random() * 80 - 40;
+        object.position.y = Math.random() * 80 - 40;
+        object.position.z = Math.random() * 80 - 40;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.x = Math.random() + 0.5;
+        object.scale.y = Math.random() + 0.5;
+        object.scale.z = Math.random() + 0.5;
+
+        scene.add(object);
+
+        // bounding volume on object level (this will reflect the current world transform)
+
+        object.userData.obb = new OBB();
+
+        objects.push(object);
+    }
+
+    //
+
+    hitbox = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true }));
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    document.addEventListener('click', onClick);
+}
+
+function onClick(event) {
+    event.preventDefault();
+
+    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+    raycaster.setFromCamera(mouse, camera);
+
+    const intersectionPoint = new THREE.Vector3();
+    const intersections = [];
+
+    for (let i = 0, il = objects.length; i < il; i++) {
+        const object = objects[i];
+        const obb = object.userData.obb;
+
+        const ray = raycaster.ray;
+
+        if (obb.intersectRay(ray, intersectionPoint) !== null) {
+            const distance = ray.origin.distanceTo(intersectionPoint);
+            intersections.push({ distance: distance, object: object });
+        }
+    }
+
+    if (intersections.length > 0) {
+        // determine closest intersection and highlight the respective 3D object
+
+        intersections.sort(sortIntersections);
+
+        intersections[0].object.add(hitbox);
+    } else {
+        const parent = hitbox.parent;
+
+        if (parent) parent.remove(hitbox);
+    }
+}
+
+function sortIntersections(a, b) {
+    return a.distance - b.distance;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    controls.update();
+
+    // transform cubes
+
+    const delta = clock.getDelta();
+
+    for (let i = 0, il = objects.length; i < il; i++) {
+        const object = objects[i];
+
+        object.rotation.x += delta * Math.PI * 0.2;
+        object.rotation.y += delta * Math.PI * 0.1;
+
+        object.updateMatrix();
+        object.updateMatrixWorld();
+
+        // update OBB
+
+        object.userData.obb.copy(object.geometry.userData.obb);
+        object.userData.obb.applyMatrix4(object.matrixWorld);
+
+        // reset
+
+        object.material.color.setHex(0x00ff00);
+    }
+
+    // collision detection
+
+    for (let i = 0, il = objects.length; i < il; i++) {
+        const object = objects[i];
+        const obb = object.userData.obb;
+
+        for (let j = i + 1, jl = objects.length; j < jl; j++) {
+            const objectToTest = objects[j];
+            const obbToTest = objectToTest.userData.obb;
+
+            // now perform intersection test
+
+            if (obb.intersectsOBB(obbToTest) === true) {
+                object.material.color.setHex(0xff0000);
+                objectToTest.material.color.setHex(0xff0000);
+            }
+        }
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_math_orientation_transform.ts b/examples-testing/examples/webgl_math_orientation_transform.ts
new file mode 100644
index 000000000..99be247d8
--- /dev/null
+++ b/examples-testing/examples/webgl_math_orientation_transform.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, mesh, target;
+
+const spherical = new THREE.Spherical();
+const rotationMatrix = new THREE.Matrix4();
+const targetQuaternion = new THREE.Quaternion();
+const clock = new THREE.Clock();
+const speed = 2;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.z = 5;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.ConeGeometry(0.1, 0.5, 8);
+    geometry.rotateX(Math.PI * 0.5);
+    const material = new THREE.MeshNormalMaterial();
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const targetGeometry = new THREE.SphereGeometry(0.05);
+    const targetMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
+    target = new THREE.Mesh(targetGeometry, targetMaterial);
+    scene.add(target);
+
+    //
+
+    const sphereGeometry = new THREE.SphereGeometry(2, 32, 32);
+    const sphereMaterial = new THREE.MeshBasicMaterial({
+        color: 0xcccccc,
+        wireframe: true,
+        transparent: true,
+        opacity: 0.3,
+    });
+    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
+    scene.add(sphere);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    generateTarget();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (!mesh.quaternion.equals(targetQuaternion)) {
+        const step = speed * delta;
+        mesh.quaternion.rotateTowards(targetQuaternion, step);
+    }
+
+    renderer.render(scene, camera);
+}
+
+function generateTarget() {
+    // generate a random point on a sphere
+
+    spherical.theta = Math.random() * Math.PI * 2;
+    spherical.phi = Math.acos(2 * Math.random() - 1);
+    spherical.radius = 2;
+
+    target.position.setFromSpherical(spherical);
+
+    // compute target rotation
+
+    rotationMatrix.lookAt(target.position, mesh.position, mesh.up);
+    targetQuaternion.setFromRotationMatrix(rotationMatrix);
+
+    setTimeout(generateTarget, 2000);
+}
diff --git a/examples-testing/examples/webgl_mesh_batch.ts b/examples-testing/examples/webgl_mesh_batch.ts
new file mode 100644
index 000000000..f93e5fb85
--- /dev/null
+++ b/examples-testing/examples/webgl_mesh_batch.ts
@@ -0,0 +1,305 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { radixSort } from 'three/addons/utils/SortUtils.js';
+
+let stats, gui, guiStatsEl;
+let camera, controls, scene, renderer;
+let geometries, mesh, material;
+const ids = [];
+const matrix = new THREE.Matrix4();
+
+//
+
+const position = new THREE.Vector3();
+const rotation = new THREE.Euler();
+const quaternion = new THREE.Quaternion();
+const scale = new THREE.Vector3();
+
+//
+
+const MAX_GEOMETRY_COUNT = 20000;
+
+const Method = {
+    BATCHED: 'BATCHED',
+    NAIVE: 'NAIVE',
+};
+
+const api = {
+    method: Method.BATCHED,
+    count: 256,
+    dynamic: 16,
+
+    sortObjects: true,
+    perObjectFrustumCulled: true,
+    opacity: 1,
+    useCustomSort: true,
+};
+
+init();
+initGeometries();
+initMesh();
+
+//
+
+function randomizeMatrix(matrix) {
+    position.x = Math.random() * 40 - 20;
+    position.y = Math.random() * 40 - 20;
+    position.z = Math.random() * 40 - 20;
+
+    rotation.x = Math.random() * 2 * Math.PI;
+    rotation.y = Math.random() * 2 * Math.PI;
+    rotation.z = Math.random() * 2 * Math.PI;
+
+    quaternion.setFromEuler(rotation);
+
+    scale.x = scale.y = scale.z = 0.5 + Math.random() * 0.5;
+
+    return matrix.compose(position, quaternion, scale);
+}
+
+function randomizeRotationSpeed(rotation) {
+    rotation.x = Math.random() * 0.01;
+    rotation.y = Math.random() * 0.01;
+    rotation.z = Math.random() * 0.01;
+    return rotation;
+}
+
+function initGeometries() {
+    geometries = [
+        new THREE.ConeGeometry(1.0, 2.0),
+        new THREE.BoxGeometry(2.0, 2.0, 2.0),
+        new THREE.SphereGeometry(1.0, 16, 8),
+    ];
+}
+
+function createMaterial() {
+    if (!material) {
+        material = new THREE.MeshNormalMaterial();
+    }
+
+    return material;
+}
+
+function cleanup() {
+    if (mesh) {
+        mesh.parent.remove(mesh);
+
+        if (mesh.dispose) {
+            mesh.dispose();
+        }
+    }
+}
+
+function initMesh() {
+    cleanup();
+
+    if (api.method === Method.BATCHED) {
+        initBatchedMesh();
+    } else {
+        initRegularMesh();
+    }
+}
+
+function initRegularMesh() {
+    mesh = new THREE.Group();
+    const material = createMaterial();
+
+    for (let i = 0; i < api.count; i++) {
+        const child = new THREE.Mesh(geometries[i % geometries.length], material);
+        randomizeMatrix(child.matrix);
+        child.matrix.decompose(child.position, child.quaternion, child.scale);
+        child.userData.rotationSpeed = randomizeRotationSpeed(new THREE.Euler());
+        mesh.add(child);
+    }
+
+    scene.add(mesh);
+}
+
+function initBatchedMesh() {
+    const geometryCount = api.count;
+    const vertexCount = geometries.length * 512;
+    const indexCount = geometries.length * 1024;
+
+    const euler = new THREE.Euler();
+    const matrix = new THREE.Matrix4();
+    mesh = new THREE.BatchedMesh(geometryCount, vertexCount, indexCount, createMaterial());
+    mesh.userData.rotationSpeeds = [];
+
+    // disable full-object frustum culling since all of the objects can be dynamic.
+    mesh.frustumCulled = false;
+
+    ids.length = 0;
+
+    const geometryIds = [
+        mesh.addGeometry(geometries[0]),
+        mesh.addGeometry(geometries[1]),
+        mesh.addGeometry(geometries[2]),
+    ];
+
+    for (let i = 0; i < api.count; i++) {
+        const id = mesh.addInstance(geometryIds[i % geometryIds.length]);
+        mesh.setMatrixAt(id, randomizeMatrix(matrix));
+
+        const rotationMatrix = new THREE.Matrix4();
+        rotationMatrix.makeRotationFromEuler(randomizeRotationSpeed(euler));
+        mesh.userData.rotationSpeeds.push(rotationMatrix);
+
+        ids.push(id);
+    }
+
+    scene.add(mesh);
+}
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
+    camera.position.z = 30;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = 1.0;
+
+    // stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // gui
+
+    gui = new GUI();
+    gui.add(api, 'count', 1, MAX_GEOMETRY_COUNT).step(1).onChange(initMesh);
+    gui.add(api, 'dynamic', 0, MAX_GEOMETRY_COUNT).step(1);
+    gui.add(api, 'method', Method).onChange(initMesh);
+    gui.add(api, 'opacity', 0, 1).onChange(v => {
+        if (v < 1) {
+            material.transparent = true;
+            material.depthWrite = false;
+        } else {
+            material.transparent = false;
+            material.depthWrite = true;
+        }
+
+        material.opacity = v;
+        material.needsUpdate = true;
+    });
+    gui.add(api, 'sortObjects');
+    gui.add(api, 'perObjectFrustumCulled');
+    gui.add(api, 'useCustomSort');
+
+    guiStatsEl = document.createElement('li');
+    guiStatsEl.classList.add('gui-stats');
+
+    // listeners
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function sortFunction(list) {
+    // initialize options
+    this._options = this._options || {
+        get: el => el.z,
+        aux: new Array(this.maxInstanceCount),
+    };
+
+    const options = this._options;
+    options.reversed = this.material.transparent;
+
+    let minZ = Infinity;
+    let maxZ = -Infinity;
+    for (let i = 0, l = list.length; i < l; i++) {
+        const z = list[i].z;
+        if (z > maxZ) maxZ = z;
+        if (z < minZ) minZ = z;
+    }
+
+    // convert depth to unsigned 32 bit range
+    const depthDelta = maxZ - minZ;
+    const factor = (2 ** 32 - 1) / depthDelta; // UINT32_MAX / z range
+    for (let i = 0, l = list.length; i < l; i++) {
+        list[i].z -= minZ;
+        list[i].z *= factor;
+    }
+
+    // perform a fast-sort using the hybrid radix sort function
+    radixSort(list, options);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    animateMeshes();
+
+    controls.update();
+    stats.update();
+
+    render();
+}
+
+function animateMeshes() {
+    const loopNum = Math.min(api.count, api.dynamic);
+
+    if (api.method === Method.BATCHED) {
+        for (let i = 0; i < loopNum; i++) {
+            const rotationMatrix = mesh.userData.rotationSpeeds[i];
+            const id = ids[i];
+
+            mesh.getMatrixAt(id, matrix);
+            matrix.multiply(rotationMatrix);
+            mesh.setMatrixAt(id, matrix);
+        }
+    } else {
+        for (let i = 0; i < loopNum; i++) {
+            const child = mesh.children[i];
+            const rotationSpeed = child.userData.rotationSpeed;
+
+            child.rotation.set(
+                child.rotation.x + rotationSpeed.x,
+                child.rotation.y + rotationSpeed.y,
+                child.rotation.z + rotationSpeed.z,
+            );
+        }
+    }
+}
+
+function render() {
+    if (mesh.isBatchedMesh) {
+        mesh.sortObjects = api.sortObjects;
+        mesh.perObjectFrustumCulled = api.perObjectFrustumCulled;
+        mesh.setCustomSort(api.useCustomSort ? sortFunction : null);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_mirror.ts b/examples-testing/examples/webgl_mirror.ts
new file mode 100644
index 000000000..8b27363a8
--- /dev/null
+++ b/examples-testing/examples/webgl_mirror.ts
@@ -0,0 +1,168 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Reflector } from 'three/addons/objects/Reflector.js';
+
+let camera, scene, renderer;
+
+let cameraControls;
+
+let sphereGroup, smallSphere;
+
+let groundMirror, verticalMirror;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.set(0, 75, 160);
+
+    cameraControls = new OrbitControls(camera, renderer.domElement);
+    cameraControls.target.set(0, 40, 0);
+    cameraControls.maxDistance = 400;
+    cameraControls.minDistance = 10;
+    cameraControls.update();
+
+    //
+
+    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
+
+    // reflectors/mirrors
+
+    let geometry, material;
+
+    geometry = new THREE.CircleGeometry(40, 64);
+    groundMirror = new Reflector(geometry, {
+        clipBias: 0.003,
+        textureWidth: window.innerWidth * window.devicePixelRatio,
+        textureHeight: window.innerHeight * window.devicePixelRatio,
+        color: 0xb5b5b5,
+    });
+    groundMirror.position.y = 0.5;
+    groundMirror.rotateX(-Math.PI / 2);
+    scene.add(groundMirror);
+
+    geometry = new THREE.PlaneGeometry(100, 100);
+    verticalMirror = new Reflector(geometry, {
+        clipBias: 0.003,
+        textureWidth: window.innerWidth * window.devicePixelRatio,
+        textureHeight: window.innerHeight * window.devicePixelRatio,
+        color: 0xc1cbcb,
+    });
+    verticalMirror.position.y = 50;
+    verticalMirror.position.z = -50;
+    scene.add(verticalMirror);
+
+    sphereGroup = new THREE.Object3D();
+    scene.add(sphereGroup);
+
+    geometry = new THREE.CylinderGeometry(0.1, 15 * Math.cos((Math.PI / 180) * 30), 0.1, 24, 1);
+    material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x8d8d8d });
+    const sphereCap = new THREE.Mesh(geometry, material);
+    sphereCap.position.y = -15 * Math.sin((Math.PI / 180) * 30) - 0.05;
+    sphereCap.rotateX(-Math.PI);
+
+    geometry = new THREE.SphereGeometry(15, 24, 24, Math.PI / 2, Math.PI * 2, 0, (Math.PI / 180) * 120);
+    const halfSphere = new THREE.Mesh(geometry, material);
+    halfSphere.add(sphereCap);
+    halfSphere.rotateX((-Math.PI / 180) * 135);
+    halfSphere.rotateZ((-Math.PI / 180) * 20);
+    halfSphere.position.y = 7.5 + 15 * Math.sin((Math.PI / 180) * 30);
+
+    sphereGroup.add(halfSphere);
+
+    geometry = new THREE.IcosahedronGeometry(5, 0);
+    material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x7b7b7b, flatShading: true });
+    smallSphere = new THREE.Mesh(geometry, material);
+    scene.add(smallSphere);
+
+    // walls
+    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeTop.position.y = 100;
+    planeTop.rotateX(Math.PI / 2);
+    scene.add(planeTop);
+
+    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeBottom.rotateX(-Math.PI / 2);
+    scene.add(planeBottom);
+
+    const planeFront = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
+    planeFront.position.z = 50;
+    planeFront.position.y = 50;
+    planeFront.rotateY(Math.PI);
+    scene.add(planeFront);
+
+    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
+    planeRight.position.x = 50;
+    planeRight.position.y = 50;
+    planeRight.rotateY(-Math.PI / 2);
+    scene.add(planeRight);
+
+    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
+    planeLeft.position.x = -50;
+    planeLeft.position.y = 50;
+    planeLeft.rotateY(Math.PI / 2);
+    scene.add(planeLeft);
+
+    // lights
+    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
+    mainLight.position.y = 60;
+    scene.add(mainLight);
+
+    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
+    greenLight.position.set(550, 50, 0);
+    scene.add(greenLight);
+
+    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
+    redLight.position.set(-550, 50, 0);
+    scene.add(redLight);
+
+    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
+    blueLight.position.set(0, 50, 550);
+    scene.add(blueLight);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    groundMirror
+        .getRenderTarget()
+        .setSize(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio);
+    verticalMirror
+        .getRenderTarget()
+        .setSize(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio);
+}
+
+function animate() {
+    const timer = Date.now() * 0.01;
+
+    sphereGroup.rotation.y -= 0.002;
+
+    smallSphere.position.set(
+        Math.cos(timer * 0.1) * 30,
+        Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
+        Math.sin(timer * 0.1) * 30,
+    );
+    smallSphere.rotation.y = Math.PI / 2 - timer * 0.1;
+    smallSphere.rotation.z = timer * 0.8;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_modifier_edgesplit.ts b/examples-testing/examples/webgl_modifier_edgesplit.ts
new file mode 100644
index 000000000..4725eff62
--- /dev/null
+++ b/examples-testing/examples/webgl_modifier_edgesplit.ts
@@ -0,0 +1,136 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { EdgeSplitModifier } from 'three/addons/modifiers/EdgeSplitModifier.js';
+import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let renderer, scene, camera;
+let modifier, mesh, baseGeometry;
+let map;
+
+const params = {
+    smoothShading: true,
+    edgeSplit: true,
+    cutOffAngle: 20,
+    showMap: false,
+    tryKeepNormals: true,
+};
+
+init();
+
+function init() {
+    const info = document.createElement('div');
+    info.style.position = 'absolute';
+    info.style.top = '10px';
+    info.style.width = '100%';
+    info.style.textAlign = 'center';
+    info.innerHTML = '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Edge Split modifier';
+    document.body.appendChild(info);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.enableDamping = true;
+    controls.dampingFactor = 0.25;
+    controls.rotateSpeed = 0.35;
+    controls.minZoom = 1;
+    camera.position.set(0, 0, 4);
+
+    scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 3));
+
+    new OBJLoader().load('./models/obj/cerberus/Cerberus.obj', function (group) {
+        const cerberus = group.children[0];
+        const modelGeometry = cerberus.geometry;
+
+        modifier = new EdgeSplitModifier();
+        baseGeometry = BufferGeometryUtils.mergeVertices(modelGeometry);
+
+        mesh = new THREE.Mesh(getGeometry(), new THREE.MeshStandardMaterial());
+        mesh.material.flatShading = !params.smoothShading;
+        mesh.rotateY(-Math.PI / 2);
+        mesh.scale.set(3.5, 3.5, 3.5);
+        mesh.translateZ(1.5);
+        scene.add(mesh);
+
+        if (map !== undefined && params.showMap) {
+            mesh.material.map = map;
+            mesh.material.needsUpdate = true;
+        }
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+
+    new THREE.TextureLoader().load('./models/obj/cerberus/Cerberus_A.jpg', function (texture) {
+        map = texture;
+        map.colorSpace = THREE.SRGBColorSpace;
+
+        if (mesh !== undefined && params.showMap) {
+            mesh.material.map = map;
+            mesh.material.needsUpdate = true;
+        }
+    });
+
+    const gui = new GUI({ title: 'Edge split modifier parameters' });
+
+    gui.add(params, 'showMap').onFinishChange(updateMesh);
+    gui.add(params, 'smoothShading').onFinishChange(updateMesh);
+    gui.add(params, 'edgeSplit').onFinishChange(updateMesh);
+    gui.add(params, 'cutOffAngle').min(0).max(180).onFinishChange(updateMesh);
+    gui.add(params, 'tryKeepNormals').onFinishChange(updateMesh);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function getGeometry() {
+    let geometry;
+
+    if (params.edgeSplit) {
+        geometry = modifier.modify(baseGeometry, (params.cutOffAngle * Math.PI) / 180, params.tryKeepNormals);
+    } else {
+        geometry = baseGeometry;
+    }
+
+    return geometry;
+}
+
+function updateMesh() {
+    if (mesh !== undefined) {
+        mesh.geometry = getGeometry();
+
+        let needsUpdate = mesh.material.flatShading === params.smoothShading;
+        mesh.material.flatShading = params.smoothShading === false;
+
+        if (map !== undefined) {
+            needsUpdate = needsUpdate || mesh.material.map !== (params.showMap ? map : null);
+            mesh.material.map = params.showMap ? map : null;
+        }
+
+        mesh.material.needsUpdate = needsUpdate;
+
+        render();
+    }
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_modifier_simplifier.ts b/examples-testing/examples/webgl_modifier_simplifier.ts
new file mode 100644
index 000000000..e6ea453b3
--- /dev/null
+++ b/examples-testing/examples/webgl_modifier_simplifier.ts
@@ -0,0 +1,77 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';
+
+let renderer, scene, camera;
+
+init();
+
+function init() {
+    const info = document.createElement('div');
+    info.style.position = 'absolute';
+    info.style.top = '10px';
+    info.style.width = '100%';
+    info.style.textAlign = 'center';
+    info.innerHTML =
+        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Vertex Reduction using SimplifyModifier';
+    document.body.appendChild(info);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 15;
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.enablePan = false;
+    controls.enableZoom = false;
+
+    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
+
+    const light = new THREE.PointLight(0xffffff, 400);
+    camera.add(light);
+    scene.add(camera);
+
+    new GLTFLoader().load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+        mesh.position.x = -3;
+        mesh.rotation.y = Math.PI / 2;
+        scene.add(mesh);
+
+        const modifier = new SimplifyModifier();
+
+        const simplified = mesh.clone();
+        simplified.material = simplified.material.clone();
+        simplified.material.flatShading = true;
+        const count = Math.floor(simplified.geometry.attributes.position.count * 0.875); // number of vertices to remove
+        simplified.geometry = modifier.modify(simplified.geometry, count);
+
+        simplified.position.x = 3;
+        simplified.rotation.y = -Math.PI / 2;
+        scene.add(simplified);
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_modifier_tessellation.ts b/examples-testing/examples/webgl_modifier_tessellation.ts
new file mode 100644
index 000000000..4600fc6cb
--- /dev/null
+++ b/examples-testing/examples/webgl_modifier_tessellation.ts
@@ -0,0 +1,142 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+import { TessellateModifier } from 'three/addons/modifiers/TessellateModifier.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+let renderer, scene, camera, stats;
+
+let controls;
+
+let mesh, uniforms;
+
+const WIDTH = window.innerWidth;
+const HEIGHT = window.innerHeight;
+
+const loader = new FontLoader();
+loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
+    init(font);
+});
+
+function init(font) {
+    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 10000);
+    camera.position.set(-100, 100, 200);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x050505);
+
+    //
+
+    let geometry = new TextGeometry('THREE.JS', {
+        font: font,
+
+        size: 40,
+        depth: 5,
+        curveSegments: 3,
+
+        bevelThickness: 2,
+        bevelSize: 1,
+        bevelEnabled: true,
+    });
+
+    geometry.center();
+
+    const tessellateModifier = new TessellateModifier(8, 6);
+
+    geometry = tessellateModifier.modify(geometry);
+
+    //
+
+    const numFaces = geometry.attributes.position.count / 3;
+
+    const colors = new Float32Array(numFaces * 3 * 3);
+    const displacement = new Float32Array(numFaces * 3 * 3);
+
+    const color = new THREE.Color();
+
+    for (let f = 0; f < numFaces; f++) {
+        const index = 9 * f;
+
+        const h = 0.2 * Math.random();
+        const s = 0.5 + 0.5 * Math.random();
+        const l = 0.5 + 0.5 * Math.random();
+
+        color.setHSL(h, s, l);
+
+        const d = 10 * (0.5 - Math.random());
+
+        for (let i = 0; i < 3; i++) {
+            colors[index + 3 * i] = color.r;
+            colors[index + 3 * i + 1] = color.g;
+            colors[index + 3 * i + 2] = color.b;
+
+            displacement[index + 3 * i] = d;
+            displacement[index + 3 * i + 1] = d;
+            displacement[index + 3 * i + 2] = d;
+        }
+    }
+
+    geometry.setAttribute('customColor', new THREE.BufferAttribute(colors, 3));
+    geometry.setAttribute('displacement', new THREE.BufferAttribute(displacement, 3));
+
+    //
+
+    uniforms = {
+        amplitude: { value: 0.0 },
+    };
+
+    const shaderMaterial = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    //
+
+    mesh = new THREE.Mesh(geometry, shaderMaterial);
+
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(WIDTH, HEIGHT);
+    renderer.setAnimationLoop(animate);
+
+    const container = document.getElementById('container');
+    container.appendChild(renderer.domElement);
+
+    controls = new TrackballControls(camera, renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.001;
+
+    uniforms.amplitude.value = 1.0 + Math.sin(time * 0.5);
+
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_morphtargets.ts b/examples-testing/examples/webgl_morphtargets.ts
new file mode 100644
index 000000000..40d605f8d
--- /dev/null
+++ b/examples-testing/examples/webgl_morphtargets.ts
@@ -0,0 +1,120 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, camera, scene, renderer, mesh;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x8fbcd4);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
+    camera.position.z = 10;
+    scene.add(camera);
+
+    scene.add(new THREE.AmbientLight(0x8fbcd4, 1.5));
+
+    const pointLight = new THREE.PointLight(0xffffff, 200);
+    camera.add(pointLight);
+
+    const geometry = createGeometry();
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xff0000,
+        flatShading: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    initGUI();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(function () {
+        renderer.render(scene, camera);
+    });
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createGeometry() {
+    const geometry = new THREE.BoxGeometry(2, 2, 2, 32, 32, 32);
+
+    // create an empty array to  hold targets for the attribute we want to morph
+    // morphing positions and normals is supported
+    geometry.morphAttributes.position = [];
+
+    // the original positions of the cube's vertices
+    const positionAttribute = geometry.attributes.position;
+
+    // for the first morph target we'll move the cube's vertices onto the surface of a sphere
+    const spherePositions = [];
+
+    // for the second morph target, we'll twist the cubes vertices
+    const twistPositions = [];
+    const direction = new THREE.Vector3(1, 0, 0);
+    const vertex = new THREE.Vector3();
+
+    for (let i = 0; i < positionAttribute.count; i++) {
+        const x = positionAttribute.getX(i);
+        const y = positionAttribute.getY(i);
+        const z = positionAttribute.getZ(i);
+
+        spherePositions.push(
+            x * Math.sqrt(1 - (y * y) / 2 - (z * z) / 2 + (y * y * z * z) / 3),
+            y * Math.sqrt(1 - (z * z) / 2 - (x * x) / 2 + (z * z * x * x) / 3),
+            z * Math.sqrt(1 - (x * x) / 2 - (y * y) / 2 + (x * x * y * y) / 3),
+        );
+
+        // stretch along the x-axis so we can see the twist better
+        vertex.set(x * 2, y, z);
+
+        vertex.applyAxisAngle(direction, (Math.PI * x) / 2).toArray(twistPositions, twistPositions.length);
+    }
+
+    // add the spherical positions as the first morph target
+    geometry.morphAttributes.position[0] = new THREE.Float32BufferAttribute(spherePositions, 3);
+
+    // add the twisted positions as the second morph target
+    geometry.morphAttributes.position[1] = new THREE.Float32BufferAttribute(twistPositions, 3);
+
+    return geometry;
+}
+
+function initGUI() {
+    // Set up dat.GUI to control targets
+    const params = {
+        Spherify: 0,
+        Twist: 0,
+    };
+    const gui = new GUI({ title: 'Morph Targets' });
+
+    gui.add(params, 'Spherify', 0, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            mesh.morphTargetInfluences[0] = value;
+        });
+    gui.add(params, 'Twist', 0, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            mesh.morphTargetInfluences[1] = value;
+        });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
diff --git a/examples-testing/examples/webgl_morphtargets_face.ts b/examples-testing/examples/webgl_morphtargets_face.ts
new file mode 100644
index 000000000..76179d902
--- /dev/null
+++ b/examples-testing/examples/webgl_morphtargets_face.ts
@@ -0,0 +1,105 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats, mixer, clock, controls;
+
+init();
+
+function init() {
+    clock = new THREE.Clock();
+
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
+    camera.position.set(-1.8, 0.8, 3);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+    container.appendChild(renderer.domElement);
+
+    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
+
+    new GLTFLoader()
+        .setKTX2Loader(ktx2Loader)
+        .setMeshoptDecoder(MeshoptDecoder)
+        .load('models/gltf/facecap.glb', gltf => {
+            const mesh = gltf.scene.children[0];
+
+            scene.add(mesh);
+
+            mixer = new THREE.AnimationMixer(mesh);
+
+            mixer.clipAction(gltf.animations[0]).play();
+
+            // GUI
+
+            const head = mesh.getObjectByName('mesh_2');
+            const influences = head.morphTargetInfluences;
+
+            const gui = new GUI();
+            gui.close();
+
+            for (const [key, value] of Object.entries(head.morphTargetDictionary)) {
+                gui.add(influences, value, 0, 1, 0.01).name(key.replace('blendShape1.', '')).listen();
+            }
+        });
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene.background = new THREE.Color(0x666666);
+    scene.environment = pmremGenerator.fromScene(environment).texture;
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 2.5;
+    controls.maxDistance = 5;
+    controls.minAzimuthAngle = -Math.PI / 2;
+    controls.maxAzimuthAngle = Math.PI / 2;
+    controls.maxPolarAngle = Math.PI / 1.8;
+    controls.target.set(0, 0.15, -0.2);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) {
+        mixer.update(delta);
+    }
+
+    renderer.render(scene, camera);
+
+    controls.update();
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_morphtargets_horse.ts b/examples-testing/examples/webgl_morphtargets_horse.ts
new file mode 100644
index 000000000..2c29e9c0e
--- /dev/null
+++ b/examples-testing/examples/webgl_morphtargets_horse.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let container, stats;
+let camera, scene, renderer;
+let mesh, mixer;
+
+const radius = 600;
+let theta = 0;
+let prevTime = Date.now();
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.y = 300;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xf0f0f0);
+
+    //
+
+    const light1 = new THREE.DirectionalLight(0xefefff, 5);
+    light1.position.set(1, 1, 1).normalize();
+    scene.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffefef, 5);
+    light2.position.set(-1, -1, -1).normalize();
+    scene.add(light2);
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/Horse.glb', function (gltf) {
+        mesh = gltf.scene.children[0];
+        mesh.scale.set(1.5, 1.5, 1.5);
+        scene.add(mesh);
+
+        mixer = new THREE.AnimationMixer(mesh);
+
+        mixer.clipAction(gltf.animations[0]).setDuration(1).play();
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    theta += 0.1;
+
+    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
+    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
+
+    camera.lookAt(0, 150, 0);
+
+    if (mixer) {
+        const time = Date.now();
+
+        mixer.update((time - prevTime) * 0.001);
+
+        prevTime = time;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_morphtargets_sphere.ts b/examples-testing/examples/webgl_morphtargets_sphere.ts
new file mode 100644
index 000000000..2b8899111
--- /dev/null
+++ b/examples-testing/examples/webgl_morphtargets_sphere.ts
@@ -0,0 +1,105 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { Timer } from 'three/addons/misc/Timer.js';
+
+let camera, scene, renderer, timer;
+
+let mesh;
+
+let sign = 1;
+const speed = 0.5;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.2, 100);
+    camera.position.set(0, 5, 5);
+
+    scene = new THREE.Scene();
+
+    timer = new Timer();
+
+    const light1 = new THREE.PointLight(0xff2200, 50000);
+    light1.position.set(100, 100, 100);
+    scene.add(light1);
+
+    const light2 = new THREE.PointLight(0x22ff00, 10000);
+    light2.position.set(-100, -100, -100);
+    scene.add(light2);
+
+    scene.add(new THREE.AmbientLight(0x111111));
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/AnimatedMorphSphere/glTF/AnimatedMorphSphere.gltf', function (gltf) {
+        mesh = gltf.scene.getObjectByName('AnimatedMorphSphere');
+        mesh.rotation.z = Math.PI / 2;
+        scene.add(mesh);
+
+        //
+
+        const pointsMaterial = new THREE.PointsMaterial({
+            size: 10,
+            sizeAttenuation: false,
+            map: new THREE.TextureLoader().load('textures/sprites/disc.png'),
+            alphaTest: 0.5,
+        });
+
+        const points = new THREE.Points(mesh.geometry, pointsMaterial);
+        points.morphTargetInfluences = mesh.morphTargetInfluences;
+        points.morphTargetDictionary = mesh.morphTargetDictionary;
+        mesh.add(points);
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 20;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    timer.update();
+    render();
+}
+
+function render() {
+    const delta = timer.getDelta();
+
+    if (mesh !== undefined) {
+        const step = delta * speed;
+
+        mesh.rotation.y += step;
+
+        mesh.morphTargetInfluences[1] = mesh.morphTargetInfluences[1] + step * sign;
+
+        if (mesh.morphTargetInfluences[1] <= 0 || mesh.morphTargetInfluences[1] >= 1) {
+            sign *= -1;
+        }
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_multiple_elements.ts b/examples-testing/examples/webgl_multiple_elements.ts
new file mode 100644
index 000000000..64f8a9c5f
--- /dev/null
+++ b/examples-testing/examples/webgl_multiple_elements.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let canvas, renderer;
+
+const scenes = [];
+
+init();
+
+function init() {
+    canvas = document.getElementById('c');
+
+    const geometries = [
+        new THREE.BoxGeometry(1, 1, 1),
+        new THREE.SphereGeometry(0.5, 12, 8),
+        new THREE.DodecahedronGeometry(0.5),
+        new THREE.CylinderGeometry(0.5, 0.5, 1, 12),
+    ];
+
+    const content = document.getElementById('content');
+
+    for (let i = 0; i < 40; i++) {
+        const scene = new THREE.Scene();
+
+        // make a list item
+        const element = document.createElement('div');
+        element.className = 'list-item';
+
+        const sceneElement = document.createElement('div');
+        element.appendChild(sceneElement);
+
+        const descriptionElement = document.createElement('div');
+        descriptionElement.innerText = 'Scene ' + (i + 1);
+        element.appendChild(descriptionElement);
+
+        // the element that represents the area we want to render the scene
+        scene.userData.element = sceneElement;
+        content.appendChild(element);
+
+        const camera = new THREE.PerspectiveCamera(50, 1, 1, 10);
+        camera.position.z = 2;
+        scene.userData.camera = camera;
+
+        const controls = new OrbitControls(scene.userData.camera, scene.userData.element);
+        controls.minDistance = 2;
+        controls.maxDistance = 5;
+        controls.enablePan = false;
+        controls.enableZoom = false;
+        scene.userData.controls = controls;
+
+        // add one random mesh to each scene
+        const geometry = geometries[(geometries.length * Math.random()) | 0];
+
+        const material = new THREE.MeshStandardMaterial({
+            color: new THREE.Color().setHSL(Math.random(), 1, 0.75, THREE.SRGBColorSpace),
+            roughness: 0.5,
+            metalness: 0,
+            flatShading: true,
+        });
+
+        scene.add(new THREE.Mesh(geometry, material));
+
+        scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, 3));
+
+        const light = new THREE.DirectionalLight(0xffffff, 1.5);
+        light.position.set(1, 1, 1);
+        scene.add(light);
+
+        scenes.push(scene);
+    }
+
+    renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
+    renderer.setClearColor(0xffffff, 1);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+}
+
+function updateSize() {
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+
+    if (canvas.width !== width || canvas.height !== height) {
+        renderer.setSize(width, height, false);
+    }
+}
+
+function animate() {
+    updateSize();
+
+    canvas.style.transform = `translateY(${window.scrollY}px)`;
+
+    renderer.setClearColor(0xffffff);
+    renderer.setScissorTest(false);
+    renderer.clear();
+
+    renderer.setClearColor(0xe0e0e0);
+    renderer.setScissorTest(true);
+
+    scenes.forEach(function (scene) {
+        // so something moves
+        scene.children[0].rotation.y = Date.now() * 0.001;
+
+        // get the element that is a place holder for where we want to
+        // draw the scene
+        const element = scene.userData.element;
+
+        // get its position relative to the page's viewport
+        const rect = element.getBoundingClientRect();
+
+        // check if it's offscreen. If so skip it
+        if (
+            rect.bottom < 0 ||
+            rect.top > renderer.domElement.clientHeight ||
+            rect.right < 0 ||
+            rect.left > renderer.domElement.clientWidth
+        ) {
+            return; // it's off screen
+        }
+
+        // set the viewport
+        const width = rect.right - rect.left;
+        const height = rect.bottom - rect.top;
+        const left = rect.left;
+        const bottom = renderer.domElement.clientHeight - rect.bottom;
+
+        renderer.setViewport(left, bottom, width, height);
+        renderer.setScissor(left, bottom, width, height);
+
+        const camera = scene.userData.camera;
+
+        //camera.aspect = width / height; // not changing in this example
+        //camera.updateProjectionMatrix();
+
+        //scene.userData.controls.update();
+
+        renderer.render(scene, camera);
+    });
+}
diff --git a/examples-testing/examples/webgl_multiple_rendertargets.ts b/examples-testing/examples/webgl_multiple_rendertargets.ts
new file mode 100644
index 000000000..86708082b
--- /dev/null
+++ b/examples-testing/examples/webgl_multiple_rendertargets.ts
@@ -0,0 +1,133 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, controls;
+let renderTarget;
+let postScene, postCamera;
+
+const parameters = {
+    samples: 4,
+    wireframe: false,
+};
+
+const gui = new GUI();
+gui.add(parameters, 'samples', 0, 4).step(1);
+gui.add(parameters, 'wireframe');
+gui.onChange(render);
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    // Create a multi render target with Float buffers
+
+    renderTarget = new THREE.WebGLRenderTarget(
+        window.innerWidth * window.devicePixelRatio,
+        window.innerHeight * window.devicePixelRatio,
+        {
+            count: 2,
+            minFilter: THREE.NearestFilter,
+            magFilter: THREE.NearestFilter,
+        },
+    );
+
+    // Name our G-Buffer attachments for debugging
+
+    renderTarget.textures[0].name = 'diffuse';
+    renderTarget.textures[1].name = 'normal';
+
+    // Scene setup
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x222222);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 50);
+    camera.position.z = 4;
+
+    const loader = new THREE.TextureLoader();
+
+    const diffuse = loader.load('textures/hardwood2_diffuse.jpg', render);
+    diffuse.wrapS = THREE.RepeatWrapping;
+    diffuse.wrapT = THREE.RepeatWrapping;
+    diffuse.colorSpace = THREE.SRGBColorSpace;
+
+    scene.add(
+        new THREE.Mesh(
+            new THREE.TorusKnotGeometry(1, 0.3, 128, 32),
+            new THREE.RawShaderMaterial({
+                name: 'G-Buffer Shader',
+                vertexShader: document.querySelector('#gbuffer-vert').textContent.trim(),
+                fragmentShader: document.querySelector('#gbuffer-frag').textContent.trim(),
+                uniforms: {
+                    tDiffuse: { value: diffuse },
+                    repeat: { value: new THREE.Vector2(5, 0.5) },
+                },
+                glslVersion: THREE.GLSL3,
+            }),
+        ),
+    );
+
+    // PostProcessing setup
+
+    postScene = new THREE.Scene();
+    postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
+
+    postScene.add(
+        new THREE.Mesh(
+            new THREE.PlaneGeometry(2, 2),
+            new THREE.RawShaderMaterial({
+                name: 'Post-FX Shader',
+                vertexShader: document.querySelector('#render-vert').textContent.trim(),
+                fragmentShader: document.querySelector('#render-frag').textContent.trim(),
+                uniforms: {
+                    tDiffuse: { value: renderTarget.textures[0] },
+                    tNormal: { value: renderTarget.textures[1] },
+                },
+                glslVersion: THREE.GLSL3,
+            }),
+        ),
+    );
+
+    // Controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    const dpr = renderer.getPixelRatio();
+    renderTarget.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
+
+    render();
+}
+
+function render() {
+    renderTarget.samples = parameters.samples;
+
+    scene.traverse(function (child) {
+        if (child.material !== undefined) {
+            child.material.wireframe = parameters.wireframe;
+        }
+    });
+
+    // render scene into target
+    renderer.setRenderTarget(renderTarget);
+    renderer.render(scene, camera);
+
+    // render post FX
+    renderer.setRenderTarget(null);
+    renderer.render(postScene, postCamera);
+}
diff --git a/examples-testing/examples/webgl_multiple_scenes_comparison.ts b/examples-testing/examples/webgl_multiple_scenes_comparison.ts
new file mode 100644
index 000000000..41a5130d4
--- /dev/null
+++ b/examples-testing/examples/webgl_multiple_scenes_comparison.ts
@@ -0,0 +1,98 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, camera, renderer, controls;
+let sceneL, sceneR;
+
+let sliderPos = window.innerWidth / 2;
+
+init();
+
+function init() {
+    container = document.querySelector('.container');
+
+    sceneL = new THREE.Scene();
+    sceneL.background = new THREE.Color(0xbcd48f);
+
+    sceneR = new THREE.Scene();
+    sceneR.background = new THREE.Color(0x8fbcd4);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 6;
+
+    controls = new OrbitControls(camera, container);
+
+    const light = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
+    light.position.set(-2, 2, 2);
+    sceneL.add(light.clone());
+    sceneR.add(light.clone());
+
+    initMeshes();
+    initSlider();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setScissorTest(true);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function initMeshes() {
+    const geometry = new THREE.IcosahedronGeometry(1, 3);
+
+    const meshL = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
+    sceneL.add(meshL);
+
+    const meshR = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ wireframe: true }));
+    sceneR.add(meshR);
+}
+
+function initSlider() {
+    const slider = document.querySelector('.slider');
+
+    function onPointerDown() {
+        if (event.isPrimary === false) return;
+
+        controls.enabled = false;
+
+        window.addEventListener('pointermove', onPointerMove);
+        window.addEventListener('pointerup', onPointerUp);
+    }
+
+    function onPointerUp() {
+        controls.enabled = true;
+
+        window.removeEventListener('pointermove', onPointerMove);
+        window.removeEventListener('pointerup', onPointerUp);
+    }
+
+    function onPointerMove(e) {
+        if (event.isPrimary === false) return;
+
+        sliderPos = Math.max(0, Math.min(window.innerWidth, e.pageX));
+
+        slider.style.left = sliderPos - slider.offsetWidth / 2 + 'px';
+    }
+
+    slider.style.touchAction = 'none'; // disable touch scroll
+    slider.addEventListener('pointerdown', onPointerDown);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.setScissor(0, 0, sliderPos, window.innerHeight);
+    renderer.render(sceneL, camera);
+
+    renderer.setScissor(sliderPos, 0, window.innerWidth, window.innerHeight);
+    renderer.render(sceneR, camera);
+}
diff --git a/examples-testing/examples/webgl_multiple_views.ts b/examples-testing/examples/webgl_multiple_views.ts
new file mode 100644
index 000000000..29126b013
--- /dev/null
+++ b/examples-testing/examples/webgl_multiple_views.ts
@@ -0,0 +1,237 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let stats;
+
+let scene, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowWidth, windowHeight;
+
+const views = [
+    {
+        left: 0,
+        bottom: 0,
+        width: 0.5,
+        height: 1.0,
+        background: new THREE.Color().setRGB(0.5, 0.5, 0.7, THREE.SRGBColorSpace),
+        eye: [0, 300, 1800],
+        up: [0, 1, 0],
+        fov: 30,
+        updateCamera: function (camera, scene, mouseX) {
+            camera.position.x += mouseX * 0.05;
+            camera.position.x = Math.max(Math.min(camera.position.x, 2000), -2000);
+            camera.lookAt(scene.position);
+        },
+    },
+    {
+        left: 0.5,
+        bottom: 0,
+        width: 0.5,
+        height: 0.5,
+        background: new THREE.Color().setRGB(0.7, 0.5, 0.5, THREE.SRGBColorSpace),
+        eye: [0, 1800, 0],
+        up: [0, 0, 1],
+        fov: 45,
+        updateCamera: function (camera, scene, mouseX) {
+            camera.position.x -= mouseX * 0.05;
+            camera.position.x = Math.max(Math.min(camera.position.x, 2000), -2000);
+            camera.lookAt(camera.position.clone().setY(0));
+        },
+    },
+    {
+        left: 0.5,
+        bottom: 0.5,
+        width: 0.5,
+        height: 0.5,
+        background: new THREE.Color().setRGB(0.5, 0.7, 0.7, THREE.SRGBColorSpace),
+        eye: [1400, 800, 1400],
+        up: [0, 1, 0],
+        fov: 60,
+        updateCamera: function (camera, scene, mouseX) {
+            camera.position.y -= mouseX * 0.05;
+            camera.position.y = Math.max(Math.min(camera.position.y, 1600), -1600);
+            camera.lookAt(scene.position);
+        },
+    },
+];
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    for (let ii = 0; ii < views.length; ++ii) {
+        const view = views[ii];
+        const camera = new THREE.PerspectiveCamera(view.fov, window.innerWidth / window.innerHeight, 1, 10000);
+        camera.position.fromArray(view.eye);
+        camera.up.fromArray(view.up);
+        view.camera = camera;
+    }
+
+    scene = new THREE.Scene();
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 0, 1);
+    scene.add(light);
+
+    // shadow
+
+    const canvas = document.createElement('canvas');
+    canvas.width = 128;
+    canvas.height = 128;
+
+    const context = canvas.getContext('2d');
+    const gradient = context.createRadialGradient(
+        canvas.width / 2,
+        canvas.height / 2,
+        0,
+        canvas.width / 2,
+        canvas.height / 2,
+        canvas.width / 2,
+    );
+    gradient.addColorStop(0.1, 'rgba(0,0,0,0.15)');
+    gradient.addColorStop(1, 'rgba(0,0,0,0)');
+
+    context.fillStyle = gradient;
+    context.fillRect(0, 0, canvas.width, canvas.height);
+
+    const shadowTexture = new THREE.CanvasTexture(canvas);
+
+    const shadowMaterial = new THREE.MeshBasicMaterial({ map: shadowTexture, transparent: true });
+    const shadowGeo = new THREE.PlaneGeometry(300, 300, 1, 1);
+
+    let shadowMesh;
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.y = -250;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.x = -400;
+    shadowMesh.position.y = -250;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
+    shadowMesh.position.x = 400;
+    shadowMesh.position.y = -250;
+    shadowMesh.rotation.x = -Math.PI / 2;
+    scene.add(shadowMesh);
+
+    const radius = 200;
+
+    const geometry1 = new THREE.IcosahedronGeometry(radius, 1);
+
+    const count = geometry1.attributes.position.count;
+    geometry1.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
+
+    const geometry2 = geometry1.clone();
+    const geometry3 = geometry1.clone();
+
+    const color = new THREE.Color();
+    const positions1 = geometry1.attributes.position;
+    const positions2 = geometry2.attributes.position;
+    const positions3 = geometry3.attributes.position;
+    const colors1 = geometry1.attributes.color;
+    const colors2 = geometry2.attributes.color;
+    const colors3 = geometry3.attributes.color;
+
+    for (let i = 0; i < count; i++) {
+        color.setHSL((positions1.getY(i) / radius + 1) / 2, 1.0, 0.5, THREE.SRGBColorSpace);
+        colors1.setXYZ(i, color.r, color.g, color.b);
+
+        color.setHSL(0, (positions2.getY(i) / radius + 1) / 2, 0.5, THREE.SRGBColorSpace);
+        colors2.setXYZ(i, color.r, color.g, color.b);
+
+        color.setRGB(1, 0.8 - (positions3.getY(i) / radius + 1) / 2, 0, THREE.SRGBColorSpace);
+        colors3.setXYZ(i, color.r, color.g, color.b);
+    }
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xffffff,
+        flatShading: true,
+        vertexColors: true,
+        shininess: 0,
+    });
+
+    const wireframeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, transparent: true });
+
+    let mesh = new THREE.Mesh(geometry1, material);
+    let wireframe = new THREE.Mesh(geometry1, wireframeMaterial);
+    mesh.add(wireframe);
+    mesh.position.x = -400;
+    mesh.rotation.x = -1.87;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry2, material);
+    wireframe = new THREE.Mesh(geometry2, wireframeMaterial);
+    mesh.add(wireframe);
+    mesh.position.x = 400;
+    scene.add(mesh);
+
+    mesh = new THREE.Mesh(geometry3, material);
+    wireframe = new THREE.Mesh(geometry3, wireframeMaterial);
+    mesh.add(wireframe);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowWidth / 2;
+    mouseY = event.clientY - windowHeight / 2;
+}
+
+function updateSize() {
+    if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
+        windowWidth = window.innerWidth;
+        windowHeight = window.innerHeight;
+
+        renderer.setSize(windowWidth, windowHeight);
+    }
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    updateSize();
+
+    for (let ii = 0; ii < views.length; ++ii) {
+        const view = views[ii];
+        const camera = view.camera;
+
+        view.updateCamera(camera, scene, mouseX, mouseY);
+
+        const left = Math.floor(windowWidth * view.left);
+        const bottom = Math.floor(windowHeight * view.bottom);
+        const width = Math.floor(windowWidth * view.width);
+        const height = Math.floor(windowHeight * view.height);
+
+        renderer.setViewport(left, bottom, width, height);
+        renderer.setScissor(left, bottom, width, height);
+        renderer.setScissorTest(true);
+        renderer.setClearColor(view.background);
+
+        camera.aspect = width / height;
+        camera.updateProjectionMatrix();
+
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgl_multisampled_renderbuffers.ts b/examples-testing/examples/webgl_multisampled_renderbuffers.ts
new file mode 100644
index 000000000..df84fb144
--- /dev/null
+++ b/examples-testing/examples/webgl_multisampled_renderbuffers.ts
@@ -0,0 +1,133 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, renderer, group, container;
+
+let composer1, composer2;
+
+const params = {
+    animate: true,
+};
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 10, 2000);
+    camera.position.z = 500;
+
+    const scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+    scene.fog = new THREE.Fog(0xcccccc, 100, 1500);
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x222222, 5);
+    hemiLight.position.set(1, 1, 1);
+    scene.add(hemiLight);
+
+    //
+
+    group = new THREE.Group();
+
+    const geometry = new THREE.SphereGeometry(10, 64, 40);
+    const material = new THREE.MeshLambertMaterial({
+        color: 0xee0808,
+        polygonOffset: true,
+        polygonOffsetFactor: 1, // positive value pushes polygon further away
+        polygonOffsetUnits: 1,
+    });
+    const material2 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
+
+    for (let i = 0; i < 50; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 600 - 300;
+        mesh.position.y = Math.random() * 600 - 300;
+        mesh.position.z = Math.random() * 600 - 300;
+        mesh.rotation.x = Math.random();
+        mesh.rotation.z = Math.random();
+        mesh.scale.setScalar(Math.random() * 5 + 5);
+        group.add(mesh);
+
+        const mesh2 = new THREE.Mesh(geometry, material2);
+        mesh2.position.copy(mesh.position);
+        mesh2.rotation.copy(mesh.rotation);
+        mesh2.scale.copy(mesh.scale);
+        group.add(mesh2);
+    }
+
+    scene.add(group);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(container.offsetWidth, container.offsetHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const size = renderer.getDrawingBufferSize(new THREE.Vector2());
+    const renderTarget = new THREE.WebGLRenderTarget(size.width, size.height, {
+        samples: 4,
+        type: THREE.HalfFloatType,
+    });
+
+    const renderPass = new RenderPass(scene, camera);
+    const outputPass = new OutputPass();
+
+    //
+
+    composer1 = new EffectComposer(renderer);
+    composer1.addPass(renderPass);
+    composer1.addPass(outputPass);
+
+    //
+
+    composer2 = new EffectComposer(renderer, renderTarget);
+    composer2.addPass(renderPass);
+    composer2.addPass(outputPass);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'animate');
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = container.offsetWidth / container.offsetHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(container.offsetWidth, container.offsetHeight);
+    composer1.setSize(container.offsetWidth, container.offsetHeight);
+    composer2.setSize(container.offsetWidth, container.offsetHeight);
+}
+
+function animate() {
+    const halfWidth = container.offsetWidth / 2;
+
+    if (params.animate) {
+        group.rotation.y += 0.002;
+    }
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, halfWidth - 1, container.offsetHeight);
+    composer1.render();
+
+    renderer.setScissor(halfWidth, 0, halfWidth, container.offsetHeight);
+    composer2.render();
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgl_panorama_cube.ts b/examples-testing/examples/webgl_panorama_cube.ts
new file mode 100644
index 000000000..efd09cfc5
--- /dev/null
+++ b/examples-testing/examples/webgl_panorama_cube.ts
@@ -0,0 +1,83 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, controls;
+let renderer;
+let scene;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 0.01;
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enablePan = false;
+    controls.enableDamping = true;
+    controls.rotateSpeed = -0.25;
+
+    const textures = getTexturesFromAtlasFile('textures/cube/sun_temple_stripe.jpg', 6);
+
+    const materials = [];
+
+    for (let i = 0; i < 6; i++) {
+        materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
+    }
+
+    const skyBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), materials);
+    skyBox.geometry.scale(1, 1, -1);
+    scene.add(skyBox);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
+    const textures = [];
+
+    for (let i = 0; i < tilesNum; i++) {
+        textures[i] = new THREE.Texture();
+    }
+
+    new THREE.ImageLoader().load(atlasImgUrl, image => {
+        let canvas, context;
+        const tileWidth = image.height;
+
+        for (let i = 0; i < textures.length; i++) {
+            canvas = document.createElement('canvas');
+            context = canvas.getContext('2d');
+            canvas.height = tileWidth;
+            canvas.width = tileWidth;
+            context.drawImage(image, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
+            textures[i].colorSpace = THREE.SRGBColorSpace;
+            textures[i].image = canvas;
+            textures[i].needsUpdate = true;
+        }
+    });
+
+    return textures;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update(); // required when damping is enabled
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_panorama_equirectangular.ts b/examples-testing/examples/webgl_panorama_equirectangular.ts
new file mode 100644
index 000000000..40796f6e2
--- /dev/null
+++ b/examples-testing/examples/webgl_panorama_equirectangular.ts
@@ -0,0 +1,112 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+
+let isUserInteracting = false,
+    onPointerDownMouseX = 0,
+    onPointerDownMouseY = 0,
+    lon = 0,
+    onPointerDownLon = 0,
+    lat = 0,
+    onPointerDownLat = 0,
+    phi = 0,
+    theta = 0;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.SphereGeometry(500, 60, 40);
+    // invert the geometry on the x-axis so that all of the faces point inward
+    geometry.scale(-1, 1, 1);
+
+    const texture = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
+    texture.colorSpace = THREE.SRGBColorSpace;
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh = new THREE.Mesh(geometry, material);
+
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointerdown', onPointerDown);
+
+    document.addEventListener('wheel', onDocumentMouseWheel);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerDown(event) {
+    if (event.isPrimary === false) return;
+
+    isUserInteracting = true;
+
+    onPointerDownMouseX = event.clientX;
+    onPointerDownMouseY = event.clientY;
+
+    onPointerDownLon = lon;
+    onPointerDownLat = lat;
+
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    lon = (onPointerDownMouseX - event.clientX) * 0.1 + onPointerDownLon;
+    lat = (event.clientY - onPointerDownMouseY) * 0.1 + onPointerDownLat;
+}
+
+function onPointerUp() {
+    if (event.isPrimary === false) return;
+
+    isUserInteracting = false;
+
+    document.removeEventListener('pointermove', onPointerMove);
+    document.removeEventListener('pointerup', onPointerUp);
+}
+
+function onDocumentMouseWheel(event) {
+    const fov = camera.fov + event.deltaY * 0.05;
+
+    camera.fov = THREE.MathUtils.clamp(fov, 10, 75);
+
+    camera.updateProjectionMatrix();
+}
+
+function animate() {
+    if (isUserInteracting === false) {
+        lon += 0.1;
+    }
+
+    lat = Math.max(-85, Math.min(85, lat));
+    phi = THREE.MathUtils.degToRad(90 - lat);
+    theta = THREE.MathUtils.degToRad(lon);
+
+    const x = 500 * Math.sin(phi) * Math.cos(theta);
+    const y = 500 * Math.cos(phi);
+    const z = 500 * Math.sin(phi) * Math.sin(theta);
+
+    camera.lookAt(x, y, z);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_performance.ts b/examples-testing/examples/webgl_performance.ts
new file mode 100644
index 000000000..3700386a3
--- /dev/null
+++ b/examples-testing/examples/webgl_performance.ts
@@ -0,0 +1,77 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(60, 60, 60);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+
+    renderer.setAnimationLoop(render);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.environment = texture;
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/');
+        loader.load('dungeon_warkarma.glb', async function (gltf) {
+            const model = gltf.scene;
+
+            // wait until the model can be added to the scene without blocking due to shader compilation
+
+            await renderer.compileAsync(model, camera, scene);
+
+            scene.add(model);
+        });
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 60;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_pmrem_test.ts b/examples-testing/examples/webgl_pmrem_test.ts
new file mode 100644
index 000000000..b33e4e2f1
--- /dev/null
+++ b/examples-testing/examples/webgl_pmrem_test.ts
@@ -0,0 +1,141 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, controls, renderer;
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const aspect = width / height;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+
+    // tonemapping
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(40, aspect, 1, 30);
+    updateCamera();
+    camera.position.set(0, 0, 16);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 4;
+    controls.maxDistance = 20;
+
+    // light
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 0); // set intensity to 0 to start
+    const x = 597;
+    const y = 213;
+    const theta = ((x + 0.5) * Math.PI) / 512;
+    const phi = ((y + 0.5) * Math.PI) / 512;
+
+    directionalLight.position.setFromSphericalCoords(100, -phi, Math.PI / 2 - theta);
+
+    scene.add(directionalLight);
+    // scene.add( new THREE.DirectionalLightHelper( directionalLight ) );
+
+    // The spot1Lux HDR environment map is expressed in nits (lux / sr). The directional light has units of lux,
+    // so to match a 1 lux light, we set a single pixel with a value equal to 1 divided by the solid
+    // angle of the pixel in steradians. This image is 1024 x 512,
+    // so the value is 1 / ( sin( phi ) * ( pi / 512 ) ^ 2 ) = 27,490 nits.
+
+    const gui = new GUI();
+    gui.add({ enabled: true }, 'enabled')
+        .name('PMREM')
+        .onChange(value => {
+            directionalLight.intensity = value ? 0 : 1;
+
+            scene.traverse(function (child) {
+                if (child.isMesh) {
+                    child.material.envMapIntensity = 1 - directionalLight.intensity;
+                }
+            });
+
+            render();
+        });
+}
+
+function createObjects() {
+    let radianceMap = null;
+    new RGBELoader()
+        // .setDataType( THREE.FloatType )
+        .setPath('textures/equirectangular/')
+        .load('spot1Lux.hdr', function (texture) {
+            radianceMap = pmremGenerator.fromEquirectangular(texture).texture;
+            pmremGenerator.dispose();
+
+            scene.background = radianceMap;
+
+            const geometry = new THREE.SphereGeometry(0.4, 32, 32);
+
+            for (let x = 0; x <= 10; x++) {
+                for (let y = 0; y <= 2; y++) {
+                    const material = new THREE.MeshPhysicalMaterial({
+                        roughness: x / 10,
+                        metalness: y < 1 ? 1 : 0,
+                        color: y < 2 ? 0xffffff : 0x000000,
+                        envMap: radianceMap,
+                        envMapIntensity: 1,
+                    });
+
+                    const mesh = new THREE.Mesh(geometry, material);
+                    mesh.position.x = x - 5;
+                    mesh.position.y = 1 - y;
+                    scene.add(mesh);
+                }
+            }
+
+            render();
+        });
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    pmremGenerator.compileEquirectangularShader();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    updateCamera();
+
+    renderer.setSize(width, height);
+
+    render();
+}
+
+function updateCamera() {
+    const horizontalFoV = 40;
+    const verticalFoV =
+        (2 * Math.atan(Math.tan(((horizontalFoV / 2) * Math.PI) / 180) / camera.aspect) * 180) / Math.PI;
+    camera.fov = verticalFoV;
+    camera.updateProjectionMatrix();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
+
+Promise.resolve().then(init).then(createObjects).then(render);
diff --git a/examples-testing/examples/webgl_points_billboards.ts b/examples-testing/examples/webgl_points_billboards.ts
new file mode 100644
index 000000000..24d4de1a9
--- /dev/null
+++ b/examples-testing/examples/webgl_points_billboards.ts
@@ -0,0 +1,120 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats, material;
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 2, 2000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.FogExp2(0x000000, 0.001);
+
+    const geometry = new THREE.BufferGeometry();
+    const vertices = [];
+
+    const sprite = new THREE.TextureLoader().load('textures/sprites/disc.png');
+    sprite.colorSpace = THREE.SRGBColorSpace;
+
+    for (let i = 0; i < 10000; i++) {
+        const x = 2000 * Math.random() - 1000;
+        const y = 2000 * Math.random() - 1000;
+        const z = 2000 * Math.random() - 1000;
+
+        vertices.push(x, y, z);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    material = new THREE.PointsMaterial({
+        size: 35,
+        sizeAttenuation: true,
+        map: sprite,
+        alphaTest: 0.5,
+        transparent: true,
+    });
+    material.color.setHSL(1.0, 0.3, 0.7, THREE.SRGBColorSpace);
+
+    const particles = new THREE.Points(geometry, material);
+    scene.add(particles);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(material, 'sizeAttenuation').onChange(function () {
+        material.needsUpdate = true;
+    });
+
+    gui.open();
+
+    //
+
+    document.body.style.touchAction = 'none';
+    document.body.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.00005;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    const h = ((360 * (1.0 + time)) % 360) / 360;
+    material.color.setHSL(h, 0.5, 0.5);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_points_sprites.ts b/examples-testing/examples/webgl_points_sprites.ts
new file mode 100644
index 000000000..31b9e2ce1
--- /dev/null
+++ b/examples-testing/examples/webgl_points_sprites.ts
@@ -0,0 +1,167 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, stats, parameters;
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+const materials = [];
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.FogExp2(0x000000, 0.0008);
+
+    const geometry = new THREE.BufferGeometry();
+    const vertices = [];
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const assignSRGB = texture => {
+        texture.colorSpace = THREE.SRGBColorSpace;
+    };
+
+    const sprite1 = textureLoader.load('textures/sprites/snowflake1.png', assignSRGB);
+    const sprite2 = textureLoader.load('textures/sprites/snowflake2.png', assignSRGB);
+    const sprite3 = textureLoader.load('textures/sprites/snowflake3.png', assignSRGB);
+    const sprite4 = textureLoader.load('textures/sprites/snowflake4.png', assignSRGB);
+    const sprite5 = textureLoader.load('textures/sprites/snowflake5.png', assignSRGB);
+
+    for (let i = 0; i < 10000; i++) {
+        const x = Math.random() * 2000 - 1000;
+        const y = Math.random() * 2000 - 1000;
+        const z = Math.random() * 2000 - 1000;
+
+        vertices.push(x, y, z);
+    }
+
+    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
+
+    parameters = [
+        [[1.0, 0.2, 0.5], sprite2, 20],
+        [[0.95, 0.1, 0.5], sprite3, 15],
+        [[0.9, 0.05, 0.5], sprite1, 10],
+        [[0.85, 0, 0.5], sprite5, 8],
+        [[0.8, 0, 0.5], sprite4, 5],
+    ];
+
+    for (let i = 0; i < parameters.length; i++) {
+        const color = parameters[i][0];
+        const sprite = parameters[i][1];
+        const size = parameters[i][2];
+
+        materials[i] = new THREE.PointsMaterial({
+            size: size,
+            map: sprite,
+            blending: THREE.AdditiveBlending,
+            depthTest: false,
+            transparent: true,
+        });
+        materials[i].color.setHSL(color[0], color[1], color[2], THREE.SRGBColorSpace);
+
+        const particles = new THREE.Points(geometry, materials[i]);
+
+        particles.rotation.x = Math.random() * 6;
+        particles.rotation.y = Math.random() * 6;
+        particles.rotation.z = Math.random() * 6;
+
+        scene.add(particles);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    const gui = new GUI();
+
+    const params = {
+        texture: true,
+    };
+
+    gui.add(params, 'texture').onChange(function (value) {
+        for (let i = 0; i < materials.length; i++) {
+            materials[i].map = value === true ? parameters[i][1] : null;
+            materials[i].needsUpdate = true;
+        }
+    });
+
+    gui.open();
+
+    document.body.style.touchAction = 'none';
+    document.body.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.00005;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0; i < scene.children.length; i++) {
+        const object = scene.children[i];
+
+        if (object instanceof THREE.Points) {
+            object.rotation.y = time * (i < 4 ? i + 1 : -(i + 1));
+        }
+    }
+
+    for (let i = 0; i < materials.length; i++) {
+        const color = parameters[i][0];
+
+        const h = ((360 * (color[0] + time)) % 360) / 360;
+        materials[i].color.setHSL(h, color[1], color[2], THREE.SRGBColorSpace);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_points_waves.ts b/examples-testing/examples/webgl_points_waves.ts
new file mode 100644
index 000000000..91986e9e9
--- /dev/null
+++ b/examples-testing/examples/webgl_points_waves.ts
@@ -0,0 +1,145 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+const SEPARATION = 100,
+    AMOUNTX = 50,
+    AMOUNTY = 50;
+
+let container, stats;
+let camera, scene, renderer;
+
+let particles,
+    count = 0;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 1000;
+
+    scene = new THREE.Scene();
+
+    //
+
+    const numParticles = AMOUNTX * AMOUNTY;
+
+    const positions = new Float32Array(numParticles * 3);
+    const scales = new Float32Array(numParticles);
+
+    let i = 0,
+        j = 0;
+
+    for (let ix = 0; ix < AMOUNTX; ix++) {
+        for (let iy = 0; iy < AMOUNTY; iy++) {
+            positions[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2; // x
+            positions[i + 1] = 0; // y
+            positions[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2; // z
+
+            scales[j] = 1;
+
+            i += 3;
+            j++;
+        }
+    }
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+    geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            color: { value: new THREE.Color(0xffffff) },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+    });
+
+    //
+
+    particles = new THREE.Points(geometry, material);
+    scene.add(particles);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    container.style.touchAction = 'none';
+    container.addEventListener('pointermove', onPointerMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+    camera.lookAt(scene.position);
+
+    const positions = particles.geometry.attributes.position.array;
+    const scales = particles.geometry.attributes.scale.array;
+
+    let i = 0,
+        j = 0;
+
+    for (let ix = 0; ix < AMOUNTX; ix++) {
+        for (let iy = 0; iy < AMOUNTY; iy++) {
+            positions[i + 1] = Math.sin((ix + count) * 0.3) * 50 + Math.sin((iy + count) * 0.5) * 50;
+
+            scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 20 + (Math.sin((iy + count) * 0.5) + 1) * 20;
+
+            i += 3;
+            j++;
+        }
+    }
+
+    particles.geometry.attributes.position.needsUpdate = true;
+    particles.geometry.attributes.scale.needsUpdate = true;
+
+    renderer.render(scene, camera);
+
+    count += 0.1;
+}
diff --git a/examples-testing/examples/webgl_portal.ts b/examples-testing/examples/webgl_portal.ts
new file mode 100644
index 000000000..4bc59593f
--- /dev/null
+++ b/examples-testing/examples/webgl_portal.ts
@@ -0,0 +1,218 @@
+import * as THREE from 'three';
+
+import * as CameraUtils from 'three/addons/utils/CameraUtils.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+let cameraControls;
+
+let smallSphereOne, smallSphereTwo;
+
+let portalCamera,
+    leftPortal,
+    rightPortal,
+    leftPortalTexture,
+    reflectedPosition,
+    rightPortalTexture,
+    bottomLeftCorner,
+    bottomRightCorner,
+    topLeftCorner;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.localClippingEnabled = true;
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
+    camera.position.set(0, 75, 160);
+
+    cameraControls = new OrbitControls(camera, renderer.domElement);
+    cameraControls.target.set(0, 40, 0);
+    cameraControls.maxDistance = 400;
+    cameraControls.minDistance = 10;
+    cameraControls.update();
+
+    //
+
+    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
+
+    // bouncing icosphere
+    const portalPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0.0);
+    const geometry = new THREE.IcosahedronGeometry(5, 0);
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xffffff,
+        emissive: 0x333333,
+        flatShading: true,
+        clippingPlanes: [portalPlane],
+        clipShadows: true,
+    });
+    smallSphereOne = new THREE.Mesh(geometry, material);
+    scene.add(smallSphereOne);
+    smallSphereTwo = new THREE.Mesh(geometry, material);
+    scene.add(smallSphereTwo);
+
+    // portals
+    portalCamera = new THREE.PerspectiveCamera(45, 1.0, 0.1, 500.0);
+    scene.add(portalCamera);
+    //frustumHelper = new THREE.CameraHelper( portalCamera );
+    //scene.add( frustumHelper );
+    bottomLeftCorner = new THREE.Vector3();
+    bottomRightCorner = new THREE.Vector3();
+    topLeftCorner = new THREE.Vector3();
+    reflectedPosition = new THREE.Vector3();
+
+    leftPortalTexture = new THREE.WebGLRenderTarget(256, 256);
+    leftPortal = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ map: leftPortalTexture.texture }));
+    leftPortal.position.x = -30;
+    leftPortal.position.y = 20;
+    leftPortal.scale.set(0.35, 0.35, 0.35);
+    scene.add(leftPortal);
+
+    rightPortalTexture = new THREE.WebGLRenderTarget(256, 256);
+    rightPortal = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ map: rightPortalTexture.texture }));
+    rightPortal.position.x = 30;
+    rightPortal.position.y = 20;
+    rightPortal.scale.set(0.35, 0.35, 0.35);
+    scene.add(rightPortal);
+
+    // walls
+    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeTop.position.y = 100;
+    planeTop.rotateX(Math.PI / 2);
+    scene.add(planeTop);
+
+    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeBottom.rotateX(-Math.PI / 2);
+    scene.add(planeBottom);
+
+    const planeFront = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
+    planeFront.position.z = 50;
+    planeFront.position.y = 50;
+    planeFront.rotateY(Math.PI);
+    scene.add(planeFront);
+
+    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff7fff }));
+    planeBack.position.z = -50;
+    planeBack.position.y = 50;
+    //planeBack.rotateY( Math.PI );
+    scene.add(planeBack);
+
+    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
+    planeRight.position.x = 50;
+    planeRight.position.y = 50;
+    planeRight.rotateY(-Math.PI / 2);
+    scene.add(planeRight);
+
+    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
+    planeLeft.position.x = -50;
+    planeLeft.position.y = 50;
+    planeLeft.rotateY(Math.PI / 2);
+    scene.add(planeLeft);
+
+    // lights
+    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
+    mainLight.position.y = 60;
+    scene.add(mainLight);
+
+    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
+    greenLight.position.set(550, 50, 0);
+    scene.add(greenLight);
+
+    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
+    redLight.position.set(-550, 50, 0);
+    scene.add(redLight);
+
+    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
+    blueLight.position.set(0, 50, 550);
+    scene.add(blueLight);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function renderPortal(thisPortalMesh, otherPortalMesh, thisPortalTexture) {
+    // set the portal camera position to be reflected about the portal plane
+    thisPortalMesh.worldToLocal(reflectedPosition.copy(camera.position));
+    reflectedPosition.x *= -1.0;
+    reflectedPosition.z *= -1.0;
+    otherPortalMesh.localToWorld(reflectedPosition);
+    portalCamera.position.copy(reflectedPosition);
+
+    // grab the corners of the other portal
+    // - note: the portal is viewed backwards; flip the left/right coordinates
+    otherPortalMesh.localToWorld(bottomLeftCorner.set(50.05, -50.05, 0.0));
+    otherPortalMesh.localToWorld(bottomRightCorner.set(-50.05, -50.05, 0.0));
+    otherPortalMesh.localToWorld(topLeftCorner.set(50.05, 50.05, 0.0));
+    // set the projection matrix to encompass the portal's frame
+    CameraUtils.frameCorners(portalCamera, bottomLeftCorner, bottomRightCorner, topLeftCorner, false);
+
+    // render the portal
+    thisPortalTexture.texture.colorSpace = renderer.outputColorSpace;
+    renderer.setRenderTarget(thisPortalTexture);
+    renderer.state.buffers.depth.setMask(true); // make sure the depth buffer is writable so it can be properly cleared, see #18897
+    if (renderer.autoClear === false) renderer.clear();
+    thisPortalMesh.visible = false; // hide this portal from its own rendering
+    renderer.render(scene, portalCamera);
+    thisPortalMesh.visible = true; // re-enable this portal's visibility for general rendering
+}
+
+function animate() {
+    // move the bouncing sphere(s)
+    const timerOne = Date.now() * 0.01;
+    const timerTwo = timerOne + Math.PI * 10.0;
+
+    smallSphereOne.position.set(
+        Math.cos(timerOne * 0.1) * 30,
+        Math.abs(Math.cos(timerOne * 0.2)) * 20 + 5,
+        Math.sin(timerOne * 0.1) * 30,
+    );
+    smallSphereOne.rotation.y = Math.PI / 2 - timerOne * 0.1;
+    smallSphereOne.rotation.z = timerOne * 0.8;
+
+    smallSphereTwo.position.set(
+        Math.cos(timerTwo * 0.1) * 30,
+        Math.abs(Math.cos(timerTwo * 0.2)) * 20 + 5,
+        Math.sin(timerTwo * 0.1) * 30,
+    );
+    smallSphereTwo.rotation.y = Math.PI / 2 - timerTwo * 0.1;
+    smallSphereTwo.rotation.z = timerTwo * 0.8;
+
+    // save the original camera properties
+    const currentRenderTarget = renderer.getRenderTarget();
+    const currentXrEnabled = renderer.xr.enabled;
+    const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+    renderer.xr.enabled = false; // Avoid camera modification
+    renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
+
+    // render the portal effect
+    renderPortal(leftPortal, rightPortal, leftPortalTexture);
+    renderPortal(rightPortal, leftPortal, rightPortalTexture);
+
+    // restore the original rendering properties
+    renderer.xr.enabled = currentXrEnabled;
+    renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+    renderer.setRenderTarget(currentRenderTarget);
+
+    // render the main scene
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_postprocessing.ts b/examples-testing/examples/webgl_postprocessing.ts
new file mode 100644
index 000000000..ecc9b28ee
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing.ts
@@ -0,0 +1,86 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+
+import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
+import { DotScreenShader } from 'three/addons/shaders/DotScreenShader.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, renderer, composer;
+let object;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    const scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 1000);
+
+    object = new THREE.Object3D();
+    scene.add(object);
+
+    const geometry = new THREE.SphereGeometry(1, 4, 4);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
+
+    for (let i = 0; i < 100; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
+        mesh.position.multiplyScalar(Math.random() * 400);
+        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
+        object.add(mesh);
+    }
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    composer.addPass(new RenderPass(scene, camera));
+
+    const effect1 = new ShaderPass(DotScreenShader);
+    effect1.uniforms['scale'].value = 4;
+    composer.addPass(effect1);
+
+    const effect2 = new ShaderPass(RGBShiftShader);
+    effect2.uniforms['amount'].value = 0.0015;
+    composer.addPass(effect2);
+
+    const effect3 = new OutputPass();
+    composer.addPass(effect3);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    object.rotation.x += 0.005;
+    object.rotation.y += 0.01;
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_advanced.ts b/examples-testing/examples/webgl_postprocessing_advanced.ts
new file mode 100644
index 000000000..adaef6208
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_advanced.ts
@@ -0,0 +1,304 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
+import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
+import { DotScreenPass } from 'three/addons/postprocessing/DotScreenPass.js';
+import { MaskPass, ClearMaskPass } from 'three/addons/postprocessing/MaskPass.js';
+import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
+
+import { BleachBypassShader } from 'three/addons/shaders/BleachBypassShader.js';
+import { ColorifyShader } from 'three/addons/shaders/ColorifyShader.js';
+import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';
+import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';
+import { SepiaShader } from 'three/addons/shaders/SepiaShader.js';
+import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
+import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let container, stats;
+
+let composerScene, composer1, composer2, composer3, composer4;
+
+let cameraOrtho, cameraPerspective, sceneModel, sceneBG, renderer, mesh, directionalLight;
+
+const width = window.innerWidth || 2;
+const height = window.innerHeight || 2;
+
+let halfWidth = width / 2;
+let halfHeight = height / 2;
+
+let quadBG, quadMask, renderScene;
+
+const delta = 0.01;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    cameraOrtho = new THREE.OrthographicCamera(-halfWidth, halfWidth, halfHeight, -halfHeight, -10000, 10000);
+    cameraOrtho.position.z = 100;
+
+    cameraPerspective = new THREE.PerspectiveCamera(50, width / height, 1, 10000);
+    cameraPerspective.position.z = 900;
+
+    //
+
+    sceneModel = new THREE.Scene();
+    sceneBG = new THREE.Scene();
+
+    //
+
+    directionalLight = new THREE.DirectionalLight(0xffffff, 3);
+    directionalLight.position.set(0, -0.1, 1).normalize();
+    sceneModel.add(directionalLight);
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
+        createMesh(gltf.scene.children[0].geometry, sceneModel, 100);
+    });
+
+    //
+
+    const diffuseMap = new THREE.TextureLoader().load('textures/cube/SwedishRoyalCastle/pz.jpg');
+    diffuseMap.colorSpace = THREE.SRGBColorSpace;
+
+    const materialColor = new THREE.MeshBasicMaterial({
+        map: diffuseMap,
+        depthTest: false,
+    });
+
+    quadBG = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), materialColor);
+    quadBG.position.z = -500;
+    quadBG.scale.set(width, height, 1);
+    sceneBG.add(quadBG);
+
+    //
+
+    const sceneMask = new THREE.Scene();
+
+    quadMask = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ color: 0xffaa00 }));
+    quadMask.position.z = -300;
+    quadMask.scale.set(width / 2, height / 2, 1);
+    sceneMask.add(quadMask);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    //
+
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    const shaderBleach = BleachBypassShader;
+    const shaderSepia = SepiaShader;
+    const shaderVignette = VignetteShader;
+
+    const effectBleach = new ShaderPass(shaderBleach);
+    const effectSepia = new ShaderPass(shaderSepia);
+    const effectVignette = new ShaderPass(shaderVignette);
+    const gammaCorrection = new ShaderPass(GammaCorrectionShader);
+
+    effectBleach.uniforms['opacity'].value = 0.95;
+
+    effectSepia.uniforms['amount'].value = 0.9;
+
+    effectVignette.uniforms['offset'].value = 0.95;
+    effectVignette.uniforms['darkness'].value = 1.6;
+
+    const effectBloom = new BloomPass(0.5);
+    const effectFilm = new FilmPass(0.35);
+    const effectFilmBW = new FilmPass(0.35, true);
+    const effectDotScreen = new DotScreenPass(new THREE.Vector2(0, 0), 0.5, 0.8);
+
+    const effectHBlur = new ShaderPass(HorizontalBlurShader);
+    const effectVBlur = new ShaderPass(VerticalBlurShader);
+    effectHBlur.uniforms['h'].value = 2 / (width / 2);
+    effectVBlur.uniforms['v'].value = 2 / (height / 2);
+
+    const effectColorify1 = new ShaderPass(ColorifyShader);
+    const effectColorify2 = new ShaderPass(ColorifyShader);
+    effectColorify1.uniforms['color'] = new THREE.Uniform(new THREE.Color(1, 0.8, 0.8));
+    effectColorify2.uniforms['color'] = new THREE.Uniform(new THREE.Color(1, 0.75, 0.5));
+
+    const clearMask = new ClearMaskPass();
+    const renderMask = new MaskPass(sceneModel, cameraPerspective);
+    const renderMaskInverse = new MaskPass(sceneModel, cameraPerspective);
+
+    renderMaskInverse.inverse = true;
+
+    //
+
+    const rtParameters = {
+        stencilBuffer: true,
+    };
+
+    const rtWidth = width / 2;
+    const rtHeight = height / 2;
+
+    //
+
+    const renderBackground = new RenderPass(sceneBG, cameraOrtho);
+    const renderModel = new RenderPass(sceneModel, cameraPerspective);
+
+    renderModel.clear = false;
+
+    composerScene = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth * 2, rtHeight * 2, rtParameters));
+
+    composerScene.addPass(renderBackground);
+    composerScene.addPass(renderModel);
+    composerScene.addPass(renderMaskInverse);
+    composerScene.addPass(effectHBlur);
+    composerScene.addPass(effectVBlur);
+    composerScene.addPass(clearMask);
+
+    //
+
+    renderScene = new TexturePass(composerScene.renderTarget2.texture);
+
+    //
+
+    composer1 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
+
+    composer1.addPass(renderScene);
+    composer1.addPass(gammaCorrection);
+    composer1.addPass(effectFilmBW);
+    composer1.addPass(effectVignette);
+
+    //
+
+    composer2 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
+
+    composer2.addPass(renderScene);
+    composer2.addPass(gammaCorrection);
+    composer2.addPass(effectDotScreen);
+    composer2.addPass(renderMask);
+    composer2.addPass(effectColorify1);
+    composer2.addPass(clearMask);
+    composer2.addPass(renderMaskInverse);
+    composer2.addPass(effectColorify2);
+    composer2.addPass(clearMask);
+    composer2.addPass(effectVignette);
+
+    //
+
+    composer3 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
+
+    composer3.addPass(renderScene);
+    composer3.addPass(gammaCorrection);
+    composer3.addPass(effectSepia);
+    composer3.addPass(effectFilm);
+    composer3.addPass(effectVignette);
+
+    //
+
+    composer4 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
+
+    composer4.addPass(renderScene);
+    composer4.addPass(gammaCorrection);
+    composer4.addPass(effectBloom);
+    composer4.addPass(effectFilm);
+    composer4.addPass(effectBleach);
+    composer4.addPass(effectVignette);
+
+    renderScene.uniforms['tDiffuse'].value = composerScene.renderTarget2.texture;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    halfWidth = window.innerWidth / 2;
+    halfHeight = window.innerHeight / 2;
+
+    cameraPerspective.aspect = window.innerWidth / window.innerHeight;
+    cameraPerspective.updateProjectionMatrix();
+
+    cameraOrtho.left = -halfWidth;
+    cameraOrtho.right = halfWidth;
+    cameraOrtho.top = halfHeight;
+    cameraOrtho.bottom = -halfHeight;
+
+    cameraOrtho.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    composerScene.setSize(halfWidth * 2, halfHeight * 2);
+
+    composer1.setSize(halfWidth, halfHeight);
+    composer2.setSize(halfWidth, halfHeight);
+    composer3.setSize(halfWidth, halfHeight);
+    composer4.setSize(halfWidth, halfHeight);
+
+    renderScene.uniforms['tDiffuse'].value = composerScene.renderTarget2.texture;
+
+    quadBG.scale.set(window.innerWidth, window.innerHeight, 1);
+    quadMask.scale.set(window.innerWidth / 2, window.innerHeight / 2, 1);
+}
+
+function createMesh(geometry, scene, scale) {
+    const diffuseMap = new THREE.TextureLoader().load('models/gltf/LeePerrySmith/Map-COL.jpg');
+    diffuseMap.colorSpace = THREE.SRGBColorSpace;
+
+    const mat2 = new THREE.MeshPhongMaterial({
+        color: 0xcbcbcb,
+        specular: 0x080808,
+        shininess: 20,
+        map: diffuseMap,
+        normalMap: new THREE.TextureLoader().load('models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg'),
+        normalScale: new THREE.Vector2(0.75, 0.75),
+    });
+
+    mesh = new THREE.Mesh(geometry, mat2);
+    mesh.position.set(0, -50, 0);
+    mesh.scale.set(scale, scale, scale);
+
+    scene.add(mesh);
+}
+
+//
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    const time = Date.now() * 0.0004;
+
+    if (mesh) mesh.rotation.y = -time;
+
+    renderer.setViewport(0, 0, halfWidth, halfHeight);
+    composerScene.render(delta);
+
+    renderer.setViewport(0, 0, halfWidth, halfHeight);
+    composer1.render(delta);
+
+    renderer.setViewport(halfWidth, 0, halfWidth, halfHeight);
+    composer2.render(delta);
+
+    renderer.setViewport(0, halfHeight, halfWidth, halfHeight);
+    composer3.render(delta);
+
+    renderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight);
+    composer4.render(delta);
+}
diff --git a/examples-testing/examples/webgl_postprocessing_afterimage.ts b/examples-testing/examples/webgl_postprocessing_afterimage.ts
new file mode 100644
index 000000000..508f90b89
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_afterimage.ts
@@ -0,0 +1,72 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, composer;
+let mesh;
+
+let afterimagePass;
+
+const params = {
+    enable: true,
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 1000);
+
+    const geometry = new THREE.BoxGeometry(150, 150, 150, 2, 2, 2);
+    const material = new THREE.MeshNormalMaterial();
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    composer.addPass(new RenderPass(scene, camera));
+
+    afterimagePass = new AfterimagePass();
+    composer.addPass(afterimagePass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI({ title: 'Damp setting' });
+    gui.add(afterimagePass.uniforms['damp'], 'value', 0, 1).step(0.001);
+    gui.add(params, 'enable');
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.rotation.x += 0.005;
+    mesh.rotation.y += 0.01;
+
+    afterimagePass.enabled = params.enable;
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_backgrounds.ts b/examples-testing/examples/webgl_postprocessing_backgrounds.ts
new file mode 100644
index 000000000..57a6a2dbd
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_backgrounds.ts
@@ -0,0 +1,214 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
+import { CubeTexturePass } from 'three/addons/postprocessing/CubeTexturePass.js';
+import { ClearPass } from 'three/addons/postprocessing/ClearPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let scene, renderer, composer;
+let clearPass, texturePass, renderPass;
+let cameraP, cubeTexturePassP;
+let gui, stats;
+
+const params = {
+    clearPass: true,
+    clearColor: 'white',
+    clearAlpha: 1.0,
+
+    texturePass: true,
+    texturePassOpacity: 1.0,
+
+    cubeTexturePass: true,
+    cubeTexturePassOpacity: 1.0,
+
+    renderPass: true,
+};
+
+init();
+
+clearGui();
+
+function clearGui() {
+    if (gui) gui.destroy();
+
+    gui = new GUI();
+
+    gui.add(params, 'clearPass');
+    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
+    gui.add(params, 'clearAlpha', 0, 1);
+
+    gui.add(params, 'texturePass');
+    gui.add(params, 'texturePassOpacity', 0, 1);
+
+    gui.add(params, 'cubeTexturePass');
+    gui.add(params, 'cubeTexturePassOpacity', 0, 1);
+
+    gui.add(params, 'renderPass');
+
+    gui.open();
+}
+
+function init() {
+    const container = document.getElementById('container');
+
+    const width = window.innerWidth || 1;
+    const height = window.innerHeight || 1;
+    const aspect = width / height;
+    const devicePixelRatio = window.devicePixelRatio || 1;
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    cameraP = new THREE.PerspectiveCamera(65, aspect, 1, 10);
+    cameraP.position.z = 7;
+
+    scene = new THREE.Scene();
+
+    const group = new THREE.Group();
+    scene.add(group);
+
+    const light = new THREE.PointLight(0xefffef, 500);
+    light.position.z = 10;
+    light.position.y = -10;
+    light.position.x = -10;
+    scene.add(light);
+
+    const light2 = new THREE.PointLight(0xffefef, 500);
+    light2.position.z = 10;
+    light2.position.x = -10;
+    light2.position.y = 10;
+    scene.add(light2);
+
+    const light3 = new THREE.PointLight(0xefefff, 500);
+    light3.position.z = 10;
+    light3.position.x = 10;
+    light3.position.y = -10;
+    scene.add(light3);
+
+    const geometry = new THREE.SphereGeometry(1, 48, 24);
+
+    const material = new THREE.MeshStandardMaterial();
+    material.roughness = 0.5 * Math.random() + 0.25;
+    material.metalness = 0;
+    material.color.setHSL(Math.random(), 1.0, 0.3);
+
+    const mesh = new THREE.Mesh(geometry, material);
+    group.add(mesh);
+
+    // postprocessing
+
+    const genCubeUrls = function (prefix, postfix) {
+        return [
+            prefix + 'px' + postfix,
+            prefix + 'nx' + postfix,
+            prefix + 'py' + postfix,
+            prefix + 'ny' + postfix,
+            prefix + 'pz' + postfix,
+            prefix + 'nz' + postfix,
+        ];
+    };
+
+    composer = new EffectComposer(renderer);
+
+    clearPass = new ClearPass(params.clearColor, params.clearAlpha);
+    composer.addPass(clearPass);
+
+    texturePass = new TexturePass();
+    composer.addPass(texturePass);
+
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
+        map.colorSpace = THREE.SRGBColorSpace;
+        texturePass.map = map;
+    });
+
+    cubeTexturePassP = null;
+
+    const ldrUrls = genCubeUrls('textures/cube/pisa/', '.png');
+    new THREE.CubeTextureLoader().load(ldrUrls, function (ldrCubeMap) {
+        cubeTexturePassP = new CubeTexturePass(cameraP, ldrCubeMap);
+        composer.insertPass(cubeTexturePassP, 2);
+    });
+
+    renderPass = new RenderPass(scene, cameraP);
+    renderPass.clear = false;
+    composer.addPass(renderPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    const controls = new OrbitControls(cameraP, renderer.domElement);
+    controls.enableZoom = false;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const aspect = width / height;
+
+    cameraP.aspect = aspect;
+    cameraP.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+
+    cameraP.updateMatrixWorld(true);
+
+    let newColor = clearPass.clearColor;
+
+    switch (params.clearColor) {
+        case 'blue':
+            newColor = 0x0000ff;
+            break;
+        case 'red':
+            newColor = 0xff0000;
+            break;
+        case 'green':
+            newColor = 0x00ff00;
+            break;
+        case 'white':
+            newColor = 0xffffff;
+            break;
+        case 'black':
+            newColor = 0x000000;
+            break;
+    }
+
+    clearPass.enabled = params.clearPass;
+    clearPass.clearColor = newColor;
+    clearPass.clearAlpha = params.clearAlpha;
+
+    texturePass.enabled = params.texturePass;
+    texturePass.opacity = params.texturePassOpacity;
+
+    if (cubeTexturePassP !== null) {
+        cubeTexturePassP.enabled = params.cubeTexturePass;
+        cubeTexturePassP.opacity = params.cubeTexturePassOpacity;
+    }
+
+    renderPass.enabled = params.renderPass;
+
+    composer.render();
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_fxaa.ts b/examples-testing/examples/webgl_postprocessing_fxaa.ts
new file mode 100644
index 000000000..55745f88e
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_fxaa.ts
@@ -0,0 +1,132 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
+
+let camera, scene, renderer, clock, group, container;
+
+let composer1, composer2, fxaaPass;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 1, 2000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d);
+    hemiLight.position.set(0, 1000, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(-3000, 1000, -1000);
+    scene.add(dirLight);
+
+    //
+
+    group = new THREE.Group();
+
+    const geometry = new THREE.TetrahedronGeometry(10);
+    const material = new THREE.MeshStandardMaterial({ color: 0xf73232, flatShading: true });
+
+    for (let i = 0; i < 100; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = Math.random() * 500 - 250;
+        mesh.position.y = Math.random() * 500 - 250;
+        mesh.position.z = Math.random() * 500 - 250;
+
+        mesh.scale.setScalar(Math.random() * 2 + 1);
+
+        mesh.rotation.x = Math.random() * Math.PI;
+        mesh.rotation.y = Math.random() * Math.PI;
+        mesh.rotation.z = Math.random() * Math.PI;
+
+        group.add(mesh);
+    }
+
+    scene.add(group);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(container.offsetWidth, container.offsetHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const renderPass = new RenderPass(scene, camera);
+    renderPass.clearAlpha = 0;
+
+    //
+
+    fxaaPass = new ShaderPass(FXAAShader);
+
+    const outputPass = new OutputPass();
+
+    composer1 = new EffectComposer(renderer);
+    composer1.addPass(renderPass);
+    composer1.addPass(outputPass);
+
+    //
+
+    const pixelRatio = renderer.getPixelRatio();
+
+    fxaaPass.material.uniforms['resolution'].value.x = 1 / (container.offsetWidth * pixelRatio);
+    fxaaPass.material.uniforms['resolution'].value.y = 1 / (container.offsetHeight * pixelRatio);
+
+    composer2 = new EffectComposer(renderer);
+    composer2.addPass(renderPass);
+    composer2.addPass(outputPass);
+
+    // FXAA is engineered to be applied towards the end of engine post processing after conversion to low dynamic range and conversion to the sRGB color space for display.
+
+    composer2.addPass(fxaaPass);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = container.offsetWidth / container.offsetHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(container.offsetWidth, container.offsetHeight);
+    composer1.setSize(container.offsetWidth, container.offsetHeight);
+    composer2.setSize(container.offsetWidth, container.offsetHeight);
+
+    const pixelRatio = renderer.getPixelRatio();
+
+    fxaaPass.material.uniforms['resolution'].value.x = 1 / (container.offsetWidth * pixelRatio);
+    fxaaPass.material.uniforms['resolution'].value.y = 1 / (container.offsetHeight * pixelRatio);
+}
+
+function animate() {
+    const halfWidth = container.offsetWidth / 2;
+
+    group.rotation.y += clock.getDelta() * 0.1;
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, halfWidth - 1, container.offsetHeight);
+    composer1.render();
+
+    renderer.setScissor(halfWidth, 0, halfWidth, container.offsetHeight);
+    composer2.render();
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgl_postprocessing_glitch.ts b/examples-testing/examples/webgl_postprocessing_glitch.ts
new file mode 100644
index 000000000..f846c0ce6
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_glitch.ts
@@ -0,0 +1,97 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, composer;
+let object, light;
+
+let glitchPass;
+
+const button = document.querySelector('#startButton');
+button.addEventListener('click', function () {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    init();
+});
+
+function updateOptions() {
+    const wildGlitch = document.getElementById('wildGlitch');
+    glitchPass.goWild = wildGlitch.checked;
+}
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 1000);
+
+    object = new THREE.Object3D();
+    scene.add(object);
+
+    const geometry = new THREE.SphereGeometry(1, 4, 4);
+
+    for (let i = 0; i < 100; i++) {
+        const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random(), flatShading: true });
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
+        mesh.position.multiplyScalar(Math.random() * 400);
+        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
+        object.add(mesh);
+    }
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    composer.addPass(new RenderPass(scene, camera));
+
+    glitchPass = new GlitchPass();
+    composer.addPass(glitchPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    const wildGlitchOption = document.getElementById('wildGlitch');
+    wildGlitchOption.addEventListener('change', updateOptions);
+
+    updateOptions();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    object.rotation.x += 0.005;
+    object.rotation.y += 0.01;
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_godrays.ts b/examples-testing/examples/webgl_postprocessing_godrays.ts
new file mode 100644
index 000000000..fb7604411
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_godrays.ts
@@ -0,0 +1,347 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import {
+    GodRaysFakeSunShader,
+    GodRaysDepthMaskShader,
+    GodRaysCombineShader,
+    GodRaysGenerateShader,
+} from 'three/addons/shaders/GodRaysShader.js';
+
+let container, stats;
+let camera, scene, renderer, materialDepth;
+
+let sphereMesh;
+
+const sunPosition = new THREE.Vector3(0, 1000, -1000);
+const clipPosition = new THREE.Vector4();
+const screenSpacePosition = new THREE.Vector3();
+
+const postprocessing = { enabled: true };
+
+const orbitRadius = 200;
+
+const bgColor = 0x000511;
+const sunColor = 0xffee00;
+
+// Use a smaller size for some of the god-ray render targets for better performance.
+const godrayRenderTargetResolutionMultiplier = 1.0 / 4.0;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 3000);
+    camera.position.z = 200;
+
+    scene = new THREE.Scene();
+
+    //
+
+    materialDepth = new THREE.MeshDepthMaterial();
+
+    // tree
+
+    const loader = new OBJLoader();
+    loader.load('models/obj/tree.obj', function (object) {
+        object.position.set(0, -150, -150);
+        object.scale.multiplyScalar(400);
+        scene.add(object);
+    });
+
+    // sphere
+
+    const geo = new THREE.SphereGeometry(1, 20, 10);
+    sphereMesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color: 0x000000 }));
+    sphereMesh.scale.multiplyScalar(20);
+    scene.add(sphereMesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setClearColor(0xffffff);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    renderer.autoClear = false;
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 50;
+    controls.maxDistance = 500;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    initPostprocessing(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function onWindowResize() {
+    const renderTargetWidth = window.innerWidth;
+    const renderTargetHeight = window.innerHeight;
+
+    camera.aspect = renderTargetWidth / renderTargetHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(renderTargetWidth, renderTargetHeight);
+    postprocessing.rtTextureColors.setSize(renderTargetWidth, renderTargetHeight);
+    postprocessing.rtTextureDepth.setSize(renderTargetWidth, renderTargetHeight);
+    postprocessing.rtTextureDepthMask.setSize(renderTargetWidth, renderTargetHeight);
+
+    const adjustedWidth = renderTargetWidth * godrayRenderTargetResolutionMultiplier;
+    const adjustedHeight = renderTargetHeight * godrayRenderTargetResolutionMultiplier;
+    postprocessing.rtTextureGodRays1.setSize(adjustedWidth, adjustedHeight);
+    postprocessing.rtTextureGodRays2.setSize(adjustedWidth, adjustedHeight);
+}
+
+function initPostprocessing(renderTargetWidth, renderTargetHeight) {
+    postprocessing.scene = new THREE.Scene();
+
+    postprocessing.camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, -10000, 10000);
+    postprocessing.camera.position.z = 100;
+
+    postprocessing.scene.add(postprocessing.camera);
+
+    postprocessing.rtTextureColors = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
+        type: THREE.HalfFloatType,
+    });
+
+    // Switching the depth formats to luminance from rgb doesn't seem to work. I didn't
+    // investigate further for now.
+    // pars.format = LuminanceFormat;
+
+    // I would have this quarter size and use it as one of the ping-pong render
+    // targets but the aliasing causes some temporal flickering
+
+    postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
+        type: THREE.HalfFloatType,
+    });
+    postprocessing.rtTextureDepthMask = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
+        type: THREE.HalfFloatType,
+    });
+
+    // The ping-pong render targets can use an adjusted resolution to minimize cost
+
+    const adjustedWidth = renderTargetWidth * godrayRenderTargetResolutionMultiplier;
+    const adjustedHeight = renderTargetHeight * godrayRenderTargetResolutionMultiplier;
+    postprocessing.rtTextureGodRays1 = new THREE.WebGLRenderTarget(adjustedWidth, adjustedHeight, {
+        type: THREE.HalfFloatType,
+    });
+    postprocessing.rtTextureGodRays2 = new THREE.WebGLRenderTarget(adjustedWidth, adjustedHeight, {
+        type: THREE.HalfFloatType,
+    });
+
+    // god-ray shaders
+
+    const godraysMaskShader = GodRaysDepthMaskShader;
+    postprocessing.godrayMaskUniforms = THREE.UniformsUtils.clone(godraysMaskShader.uniforms);
+    postprocessing.materialGodraysDepthMask = new THREE.ShaderMaterial({
+        uniforms: postprocessing.godrayMaskUniforms,
+        vertexShader: godraysMaskShader.vertexShader,
+        fragmentShader: godraysMaskShader.fragmentShader,
+    });
+
+    const godraysGenShader = GodRaysGenerateShader;
+    postprocessing.godrayGenUniforms = THREE.UniformsUtils.clone(godraysGenShader.uniforms);
+    postprocessing.materialGodraysGenerate = new THREE.ShaderMaterial({
+        uniforms: postprocessing.godrayGenUniforms,
+        vertexShader: godraysGenShader.vertexShader,
+        fragmentShader: godraysGenShader.fragmentShader,
+    });
+
+    const godraysCombineShader = GodRaysCombineShader;
+    postprocessing.godrayCombineUniforms = THREE.UniformsUtils.clone(godraysCombineShader.uniforms);
+    postprocessing.materialGodraysCombine = new THREE.ShaderMaterial({
+        uniforms: postprocessing.godrayCombineUniforms,
+        vertexShader: godraysCombineShader.vertexShader,
+        fragmentShader: godraysCombineShader.fragmentShader,
+    });
+
+    const godraysFakeSunShader = GodRaysFakeSunShader;
+    postprocessing.godraysFakeSunUniforms = THREE.UniformsUtils.clone(godraysFakeSunShader.uniforms);
+    postprocessing.materialGodraysFakeSun = new THREE.ShaderMaterial({
+        uniforms: postprocessing.godraysFakeSunUniforms,
+        vertexShader: godraysFakeSunShader.vertexShader,
+        fragmentShader: godraysFakeSunShader.fragmentShader,
+    });
+
+    postprocessing.godraysFakeSunUniforms.bgColor.value.setHex(bgColor);
+    postprocessing.godraysFakeSunUniforms.sunColor.value.setHex(sunColor);
+
+    postprocessing.godrayCombineUniforms.fGodRayIntensity.value = 0.75;
+
+    postprocessing.quad = new THREE.Mesh(new THREE.PlaneGeometry(1.0, 1.0), postprocessing.materialGodraysGenerate);
+    postprocessing.quad.position.z = -9900;
+    postprocessing.scene.add(postprocessing.quad);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function getStepSize(filterLen, tapsPerPass, pass) {
+    return filterLen * Math.pow(tapsPerPass, -pass);
+}
+
+function filterGodRays(inputTex, renderTarget, stepSize) {
+    postprocessing.scene.overrideMaterial = postprocessing.materialGodraysGenerate;
+
+    postprocessing.godrayGenUniforms['fStepSize'].value = stepSize;
+    postprocessing.godrayGenUniforms['tInput'].value = inputTex;
+
+    renderer.setRenderTarget(renderTarget);
+    renderer.render(postprocessing.scene, postprocessing.camera);
+    postprocessing.scene.overrideMaterial = null;
+}
+
+function render() {
+    const time = Date.now() / 4000;
+
+    sphereMesh.position.x = orbitRadius * Math.cos(time);
+    sphereMesh.position.z = orbitRadius * Math.sin(time) - 100;
+
+    if (postprocessing.enabled) {
+        clipPosition.x = sunPosition.x;
+        clipPosition.y = sunPosition.y;
+        clipPosition.z = sunPosition.z;
+        clipPosition.w = 1;
+
+        clipPosition.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
+
+        // perspective divide (produce NDC space)
+
+        clipPosition.x /= clipPosition.w;
+        clipPosition.y /= clipPosition.w;
+
+        screenSpacePosition.x = (clipPosition.x + 1) / 2; // transform from [-1,1] to [0,1]
+        screenSpacePosition.y = (clipPosition.y + 1) / 2; // transform from [-1,1] to [0,1]
+        screenSpacePosition.z = clipPosition.z; // needs to stay in clip space for visibilty checks
+
+        // Give it to the god-ray and sun shaders
+
+        postprocessing.godrayGenUniforms['vSunPositionScreenSpace'].value.copy(screenSpacePosition);
+        postprocessing.godraysFakeSunUniforms['vSunPositionScreenSpace'].value.copy(screenSpacePosition);
+
+        // -- Draw sky and sun --
+
+        // Clear colors and depths, will clear to sky color
+
+        renderer.setRenderTarget(postprocessing.rtTextureColors);
+        renderer.clear(true, true, false);
+
+        // Sun render. Runs a shader that gives a brightness based on the screen
+        // space distance to the sun. Not very efficient, so i make a scissor
+        // rectangle around the suns position to avoid rendering surrounding pixels.
+
+        const sunsqH = 0.74 * window.innerHeight; // 0.74 depends on extent of sun from shader
+        const sunsqW = 0.74 * window.innerHeight; // both depend on height because sun is aspect-corrected
+
+        screenSpacePosition.x *= window.innerWidth;
+        screenSpacePosition.y *= window.innerHeight;
+
+        renderer.setScissor(screenSpacePosition.x - sunsqW / 2, screenSpacePosition.y - sunsqH / 2, sunsqW, sunsqH);
+        renderer.setScissorTest(true);
+
+        postprocessing.godraysFakeSunUniforms['fAspect'].value = window.innerWidth / window.innerHeight;
+
+        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysFakeSun;
+        renderer.setRenderTarget(postprocessing.rtTextureColors);
+        renderer.render(postprocessing.scene, postprocessing.camera);
+
+        renderer.setScissorTest(false);
+
+        // -- Draw scene objects --
+
+        // Colors
+
+        scene.overrideMaterial = null;
+        renderer.setRenderTarget(postprocessing.rtTextureColors);
+        renderer.render(scene, camera);
+
+        // Depth
+
+        scene.overrideMaterial = materialDepth;
+        renderer.setRenderTarget(postprocessing.rtTextureDepth);
+        renderer.clear();
+        renderer.render(scene, camera);
+
+        //
+
+        postprocessing.godrayMaskUniforms['tInput'].value = postprocessing.rtTextureDepth.texture;
+
+        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysDepthMask;
+        renderer.setRenderTarget(postprocessing.rtTextureDepthMask);
+        renderer.render(postprocessing.scene, postprocessing.camera);
+
+        // -- Render god-rays --
+
+        // Maximum length of god-rays (in texture space [0,1]X[0,1])
+
+        const filterLen = 1.0;
+
+        // Samples taken by filter
+
+        const TAPS_PER_PASS = 6.0;
+
+        // Pass order could equivalently be 3,2,1 (instead of 1,2,3), which
+        // would start with a small filter support and grow to large. however
+        // the large-to-small order produces less objectionable aliasing artifacts that
+        // appear as a glimmer along the length of the beams
+
+        // pass 1 - render into first ping-pong target
+        filterGodRays(
+            postprocessing.rtTextureDepthMask.texture,
+            postprocessing.rtTextureGodRays2,
+            getStepSize(filterLen, TAPS_PER_PASS, 1.0),
+        );
+
+        // pass 2 - render into second ping-pong target
+        filterGodRays(
+            postprocessing.rtTextureGodRays2.texture,
+            postprocessing.rtTextureGodRays1,
+            getStepSize(filterLen, TAPS_PER_PASS, 2.0),
+        );
+
+        // pass 3 - 1st RT
+        filterGodRays(
+            postprocessing.rtTextureGodRays1.texture,
+            postprocessing.rtTextureGodRays2,
+            getStepSize(filterLen, TAPS_PER_PASS, 3.0),
+        );
+
+        // final pass - composite god-rays onto colors
+
+        postprocessing.godrayCombineUniforms['tColors'].value = postprocessing.rtTextureColors.texture;
+        postprocessing.godrayCombineUniforms['tGodRays'].value = postprocessing.rtTextureGodRays2.texture;
+
+        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysCombine;
+
+        renderer.setRenderTarget(null);
+        renderer.render(postprocessing.scene, postprocessing.camera);
+        postprocessing.scene.overrideMaterial = null;
+    } else {
+        renderer.setRenderTarget(null);
+        renderer.clear();
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgl_postprocessing_gtao.ts b/examples-testing/examples/webgl_postprocessing_gtao.ts
new file mode 100644
index 000000000..4f16d1554
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_gtao.ts
@@ -0,0 +1,215 @@
+import * as THREE from 'three';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, composer, controls, clock, stats, mixer;
+
+init();
+
+function init() {
+    const dracoLoader = new DRACOLoader();
+    dracoLoader.setDecoderPath('jsm/libs/draco/');
+    dracoLoader.setDecoderConfig({ type: 'js' });
+    const loader = new GLTFLoader();
+    loader.setDRACOLoader(dracoLoader);
+    loader.setPath('models/gltf/');
+
+    clock = new THREE.Clock();
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xbfe3dd);
+    scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(5, 2, 8);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0.5, 0);
+    controls.update();
+    controls.enablePan = false;
+    controls.enableDamping = true;
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    composer = new EffectComposer(renderer);
+
+    const renderPass = new RenderPass(scene, camera);
+    composer.addPass(renderPass);
+
+    const gtaoPass = new GTAOPass(scene, camera, width, height);
+    gtaoPass.output = GTAOPass.OUTPUT.Denoise;
+    composer.addPass(gtaoPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    //
+
+    loader.load(
+        'LittlestTokyo.glb',
+        gltf => {
+            const model = gltf.scene;
+            model.position.set(1, 1, 0);
+            model.scale.set(0.01, 0.01, 0.01);
+            scene.add(model);
+
+            mixer = new THREE.AnimationMixer(model);
+            mixer.clipAction(gltf.animations[0]).play();
+
+            const box = new THREE.Box3().setFromObject(scene);
+            gtaoPass.setSceneClipBox(box);
+        },
+        undefined,
+        e => console.error(e),
+    );
+
+    // Init gui
+    const gui = new GUI();
+
+    gui.add(gtaoPass, 'output', {
+        Default: GTAOPass.OUTPUT.Default,
+        Diffuse: GTAOPass.OUTPUT.Diffuse,
+        'AO Only': GTAOPass.OUTPUT.AO,
+        'AO Only + Denoise': GTAOPass.OUTPUT.Denoise,
+        Depth: GTAOPass.OUTPUT.Depth,
+        Normal: GTAOPass.OUTPUT.Normal,
+    }).onChange(function (value) {
+        gtaoPass.output = value;
+    });
+
+    const aoParameters = {
+        radius: 0.25,
+        distanceExponent: 1,
+        thickness: 1,
+        scale: 1,
+        samples: 16,
+        distanceFallOff: 1,
+        screenSpaceRadius: false,
+    };
+    const pdParameters = {
+        lumaPhi: 10,
+        depthPhi: 2,
+        normalPhi: 3,
+        radius: 4,
+        radiusExponent: 1,
+        rings: 2,
+        samples: 16,
+    };
+    gtaoPass.updateGtaoMaterial(aoParameters);
+    gtaoPass.updatePdMaterial(pdParameters);
+    gui.add(gtaoPass, 'blendIntensity').min(0).max(1).step(0.01);
+    gui.add(aoParameters, 'radius')
+        .min(0.01)
+        .max(1)
+        .step(0.01)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'distanceExponent')
+        .min(1)
+        .max(4)
+        .step(0.01)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'thickness')
+        .min(0.01)
+        .max(10)
+        .step(0.01)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'distanceFallOff')
+        .min(0)
+        .max(1)
+        .step(0.01)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'scale')
+        .min(0.01)
+        .max(2.0)
+        .step(0.01)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'samples')
+        .min(2)
+        .max(32)
+        .step(1)
+        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(aoParameters, 'screenSpaceRadius').onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
+    gui.add(pdParameters, 'lumaPhi')
+        .min(0)
+        .max(20)
+        .step(0.01)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'depthPhi')
+        .min(0.01)
+        .max(20)
+        .step(0.01)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'normalPhi')
+        .min(0.01)
+        .max(20)
+        .step(0.01)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'radius')
+        .min(0)
+        .max(32)
+        .step(1)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'radiusExponent')
+        .min(0.1)
+        .max(4)
+        .step(0.1)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'rings')
+        .min(1)
+        .max(16)
+        .step(0.125)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+    gui.add(pdParameters, 'samples')
+        .min(2)
+        .max(32)
+        .step(1)
+        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) {
+        mixer.update(delta);
+    }
+
+    controls.update();
+
+    stats.begin();
+    composer.render();
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_masking.ts b/examples-testing/examples/webgl_postprocessing_masking.ts
new file mode 100644
index 000000000..f6e7310bf
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_masking.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
+import { ClearPass } from 'three/addons/postprocessing/ClearPass.js';
+import { MaskPass, ClearMaskPass } from 'three/addons/postprocessing/MaskPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, composer, renderer;
+let box, torus;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 10;
+
+    const scene1 = new THREE.Scene();
+    const scene2 = new THREE.Scene();
+
+    box = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 4));
+    scene1.add(box);
+
+    torus = new THREE.Mesh(new THREE.TorusGeometry(3, 1, 16, 32));
+    scene2.add(torus);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setClearColor(0xe0e0e0);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const clearPass = new ClearPass();
+
+    const clearMaskPass = new ClearMaskPass();
+
+    const maskPass1 = new MaskPass(scene1, camera);
+    const maskPass2 = new MaskPass(scene2, camera);
+
+    const texture1 = new THREE.TextureLoader().load('textures/758px-Canestra_di_frutta_(Caravaggio).jpg');
+    texture1.colorSpace = THREE.SRGBColorSpace;
+    texture1.minFilter = THREE.LinearFilter;
+    const texture2 = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
+    texture2.colorSpace = THREE.SRGBColorSpace;
+
+    const texturePass1 = new TexturePass(texture1);
+    const texturePass2 = new TexturePass(texture2);
+
+    const outputPass = new OutputPass();
+
+    const parameters = {
+        stencilBuffer: true,
+    };
+
+    const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, parameters);
+
+    composer = new EffectComposer(renderer, renderTarget);
+    composer.addPass(clearPass);
+    composer.addPass(maskPass1);
+    composer.addPass(texturePass1);
+    composer.addPass(clearMaskPass);
+    composer.addPass(maskPass2);
+    composer.addPass(texturePass2);
+    composer.addPass(clearMaskPass);
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    const time = performance.now() * 0.001 + 6000;
+
+    box.position.x = Math.cos(time / 1.5) * 2;
+    box.position.y = Math.sin(time) * 2;
+    box.rotation.x = time;
+    box.rotation.y = time / 2;
+
+    torus.position.x = Math.cos(time) * 2;
+    torus.position.y = Math.sin(time / 1.5) * 2;
+    torus.rotation.x = time;
+    torus.rotation.y = time / 2;
+
+    renderer.clear();
+    composer.render(time);
+}
diff --git a/examples-testing/examples/webgl_postprocessing_material_ao.ts b/examples-testing/examples/webgl_postprocessing_material_ao.ts
new file mode 100644
index 000000000..2f17a5304
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_material_ao.ts
@@ -0,0 +1,277 @@
+import * as THREE from 'three';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { MeshPostProcessingMaterial } from 'three/addons/materials/MeshPostProcessingMaterial.js';
+
+let renderer, camera, scene, composer, controls, stats;
+const sceneParameters = {
+    output: 0,
+    envMapIntensity: 1.0,
+    ambientLightIntensity: 0.0,
+    lightIntensity: 50,
+    shadow: true,
+};
+const aoParameters = {
+    radius: 0.5,
+    distanceExponent: 2,
+    thickness: 10,
+    scale: 1,
+    samples: 16,
+    distanceFallOff: 1,
+};
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+    renderer.shadowMap.enabled = sceneParameters.shadow;
+
+    const plyLoader = new PLYLoader();
+    const rgbeloader = new RGBELoader();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 50);
+    camera.position.set(0, 3, 5);
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 1, 0);
+    controls.update();
+    controls.enablePan = false;
+    controls.enableDamping = true;
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    scene = new THREE.Scene();
+    composer = new EffectComposer(renderer);
+
+    const gtaoPass = new GTAOPass(scene, camera, width, height);
+    gtaoPass.output = GTAOPass.OUTPUT.Off;
+    const renderPasse = new RenderPass(scene, camera);
+    const outputPass = new OutputPass();
+
+    composer.addPass(gtaoPass);
+    composer.addPass(renderPasse);
+    composer.addPass(outputPass);
+
+    rgbeloader.load('textures/equirectangular/royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+        scene.environment = texture;
+    });
+
+    const groundMaterial = new MeshPostProcessingMaterial({
+        color: 0x7f7f7f,
+        envMapIntensity: sceneParameters.envMapIntensity,
+        aoPassMap: gtaoPass.gtaoMap,
+    });
+    const objectMaterial = new MeshPostProcessingMaterial({
+        color: 0xffffff,
+        roughness: 0.5,
+        metalness: 0.5,
+        envMapIntensity: sceneParameters.envMapIntensity,
+        aoPassMap: gtaoPass.gtaoMap,
+    });
+    const emissiveMaterial = new MeshPostProcessingMaterial({
+        color: 0,
+        emissive: 0xffffff,
+        aoPassMap: gtaoPass.gtaoMap,
+    });
+    plyLoader.load('models/ply/binary/Lucy100k.ply', geometry => {
+        geometry.computeVertexNormals();
+        const lucy = new THREE.Mesh(geometry, objectMaterial);
+        lucy.receiveShadow = true;
+        lucy.castShadow = true;
+        lucy.scale.setScalar(0.001);
+        lucy.rotation.set(0, Math.PI, 0);
+        lucy.position.set(0.04, 1.8, 0.02);
+        scene.add(lucy);
+    });
+    const ambientLight = new THREE.AmbientLight(0xffffff, sceneParameters.ambientLightIntensity);
+    const lightGroup = new THREE.Group();
+    const planeGeometry = new THREE.PlaneGeometry(6, 6);
+    const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 64);
+    const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
+    const lightSphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
+    scene.background = new THREE.Color(0xbfe3dd);
+    scene.add(ambientLight);
+    scene.add(lightGroup);
+    const targetObject = new THREE.Object3D();
+    targetObject.position.set(0, 1, 0);
+    scene.add(targetObject);
+    const lightColors = [0xff4040, 0x40ff40, 0x4040ff];
+    for (let j = 0; j < 3; ++j) {
+        const light = new THREE.SpotLight(lightColors[j], sceneParameters.lightIntensity, 0, Math.PI / 9);
+        light.castShadow = true;
+        light.shadow.camera.far = 15;
+        light.position.set(5 * Math.cos((Math.PI * j * 2) / 3), 2.5, 5 * Math.sin((Math.PI * j * 2) / 3));
+        light.target = targetObject;
+        lightGroup.add(light);
+    }
+
+    const groundPlane = new THREE.Mesh(planeGeometry, groundMaterial);
+    groundPlane.rotation.x = -Math.PI / 2;
+    groundPlane.position.set(0, 0, 0);
+    groundPlane.receiveShadow = true;
+    scene.add(groundPlane);
+    const pedestal = new THREE.Mesh(cylinderGeometry, groundMaterial);
+    pedestal.position.set(0, 0.5, 0);
+    pedestal.receiveShadow = true;
+    pedestal.castShadow = true;
+    scene.add(pedestal);
+    const sphereMesh = new THREE.InstancedMesh(sphereGeometry, objectMaterial, 6);
+    sphereMesh.receiveShadow = true;
+    sphereMesh.castShadow = true;
+    scene.add(sphereMesh);
+    [...Array(6).keys()].forEach(i =>
+        sphereMesh.setMatrixAt(
+            i,
+            new THREE.Matrix4().makeTranslation(Math.cos((Math.PI * i) / 3), 0.5, Math.sin((Math.PI * i) / 3)),
+        ),
+    );
+    const lightSphereMesh = new THREE.InstancedMesh(lightSphereGeometry, emissiveMaterial, 4);
+    scene.add(lightSphereMesh);
+    [...Array(4).keys()].forEach(i =>
+        lightSphereMesh.setMatrixAt(
+            i,
+            new THREE.Matrix4().makeTranslation(
+                0.4 * Math.cos((Math.PI * (i + 0.5)) / 2),
+                1.1,
+                0.45 * Math.sin((Math.PI * (i + 0.5)) / 2),
+            ),
+        ),
+    );
+
+    const updateGtaoMaterial = () => gtaoPass.updateGtaoMaterial(aoParameters);
+    const updateOutput = () => {
+        composer.removePass(gtaoPass);
+        composer.insertPass(gtaoPass, sceneParameters.output == 1 ? 1 : 0);
+
+        switch (sceneParameters.output) {
+            default:
+            case 0:
+                gtaoPass.output = GTAOPass.OUTPUT.Off;
+                gtaoPass.enabled = true;
+                renderPasse.enabled = true;
+                break;
+            case 1:
+                gtaoPass.output = GTAOPass.OUTPUT.Default;
+                gtaoPass.enabled = true;
+                renderPasse.enabled = true;
+                break;
+            case 2:
+                gtaoPass.output = GTAOPass.OUTPUT.Diffuse;
+                gtaoPass.enabled = false;
+                renderPasse.enabled = true;
+                break;
+            case 3:
+                gtaoPass.output = GTAOPass.OUTPUT.Denoise;
+                gtaoPass.enabled = true;
+                renderPasse.enabled = false;
+                break;
+        }
+
+        groundMaterial.aoPassMap = sceneParameters.output === 0 ? gtaoPass.gtaoMap : null;
+        objectMaterial.aoPassMap = sceneParameters.output === 0 ? gtaoPass.gtaoMap : null;
+    };
+
+    updateOutput();
+    updateGtaoMaterial();
+
+    const gui = new GUI();
+    gui.add(sceneParameters, 'output', {
+        'material AO': 0,
+        'post blended AO': 1,
+        'only diffuse': 2,
+        'only AO': 3,
+    }).onChange(() => updateOutput());
+    gui.add(sceneParameters, 'envMapIntensity')
+        .min(0)
+        .max(1)
+        .step(0.01)
+        .onChange(() => {
+            groundMaterial.envMapIntensity = sceneParameters.envMapIntensity;
+            objectMaterial.envMapIntensity = sceneParameters.envMapIntensity;
+        });
+    gui.add(sceneParameters, 'ambientLightIntensity')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(() => {
+            ambientLight.intensity = sceneParameters.ambientLightIntensity;
+        });
+    gui.add(sceneParameters, 'lightIntensity')
+        .min(0)
+        .max(100)
+        .step(1)
+        .onChange(() => {
+            lightGroup.children.forEach(light => (light.intensity = sceneParameters.lightIntensity));
+        });
+    gui.add(sceneParameters, 'shadow').onChange(value => {
+        renderer.shadowMap.enabled = value;
+        lightGroup.children.forEach(light => (light.castShadow = value));
+    });
+    gui.add(aoParameters, 'radius')
+        .min(0.01)
+        .max(2)
+        .step(0.01)
+        .onChange(() => updateGtaoMaterial());
+    gui.add(aoParameters, 'distanceExponent')
+        .min(1)
+        .max(4)
+        .step(0.01)
+        .onChange(() => updateGtaoMaterial());
+    gui.add(aoParameters, 'thickness')
+        .min(0.01)
+        .max(10)
+        .step(0.01)
+        .onChange(() => updateGtaoMaterial());
+    gui.add(aoParameters, 'distanceFallOff')
+        .min(0)
+        .max(1)
+        .step(0.01)
+        .onChange(() => updateGtaoMaterial());
+    gui.add(aoParameters, 'scale')
+        .min(0.01)
+        .max(2.0)
+        .step(0.01)
+        .onChange(() => updateGtaoMaterial());
+    gui.add(aoParameters, 'samples')
+        .min(2)
+        .max(32)
+        .step(1)
+        .onChange(() => updateGtaoMaterial());
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    controls.update();
+    stats.begin();
+    composer.render();
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_outline.ts b/examples-testing/examples/webgl_postprocessing_outline.ts
new file mode 100644
index 000000000..356575460
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_outline.ts
@@ -0,0 +1,282 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
+
+let container, stats;
+let camera, scene, renderer, controls;
+let composer, effectFXAA, outlinePass;
+
+let selectedObjects = [];
+
+const raycaster = new THREE.Raycaster();
+const mouse = new THREE.Vector2();
+
+const obj3d = new THREE.Object3D();
+const group = new THREE.Group();
+
+const params = {
+    edgeStrength: 3.0,
+    edgeGlow: 0.0,
+    edgeThickness: 1.0,
+    pulsePeriod: 0,
+    rotate: false,
+    usePatternTexture: false,
+};
+
+// Init gui
+
+const gui = new GUI({ width: 280 });
+
+gui.add(params, 'edgeStrength', 0.01, 10).onChange(function (value) {
+    outlinePass.edgeStrength = Number(value);
+});
+
+gui.add(params, 'edgeGlow', 0.0, 1).onChange(function (value) {
+    outlinePass.edgeGlow = Number(value);
+});
+
+gui.add(params, 'edgeThickness', 1, 4).onChange(function (value) {
+    outlinePass.edgeThickness = Number(value);
+});
+
+gui.add(params, 'pulsePeriod', 0.0, 5).onChange(function (value) {
+    outlinePass.pulsePeriod = Number(value);
+});
+
+gui.add(params, 'rotate');
+
+gui.add(params, 'usePatternTexture').onChange(function (value) {
+    outlinePass.usePatternTexture = value;
+});
+
+function Configuration() {
+    this.visibleEdgeColor = '#ffffff';
+    this.hiddenEdgeColor = '#190a05';
+}
+
+const conf = new Configuration();
+
+gui.addColor(conf, 'visibleEdgeColor').onChange(function (value) {
+    outlinePass.visibleEdgeColor.set(value);
+});
+
+gui.addColor(conf, 'hiddenEdgeColor').onChange(function (value) {
+    outlinePass.hiddenEdgeColor.set(value);
+});
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.shadowMap.enabled = true;
+    // todo - support pixelRatio in this demo
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100);
+    camera.position.set(0, 0, 8);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 5;
+    controls.maxDistance = 20;
+    controls.enablePan = false;
+    controls.enableDamping = true;
+    controls.dampingFactor = 0.05;
+
+    //
+
+    scene.add(new THREE.AmbientLight(0xaaaaaa, 0.6));
+
+    const light = new THREE.DirectionalLight(0xddffdd, 2);
+    light.position.set(1, 1, 1);
+    light.castShadow = true;
+    light.shadow.mapSize.width = 1024;
+    light.shadow.mapSize.height = 1024;
+
+    const d = 10;
+
+    light.shadow.camera.left = -d;
+    light.shadow.camera.right = d;
+    light.shadow.camera.top = d;
+    light.shadow.camera.bottom = -d;
+    light.shadow.camera.far = 1000;
+
+    scene.add(light);
+
+    // model
+
+    const loader = new OBJLoader();
+    loader.load('models/obj/tree.obj', function (object) {
+        let scale = 1.0;
+
+        object.traverse(function (child) {
+            if (child instanceof THREE.Mesh) {
+                child.geometry.center();
+                child.geometry.computeBoundingSphere();
+                scale = 0.2 * child.geometry.boundingSphere.radius;
+
+                const phongMaterial = new THREE.MeshPhongMaterial({
+                    color: 0xffffff,
+                    specular: 0x111111,
+                    shininess: 5,
+                });
+                child.material = phongMaterial;
+                child.receiveShadow = true;
+                child.castShadow = true;
+            }
+        });
+
+        object.position.y = 1;
+        object.scale.divideScalar(scale);
+        obj3d.add(object);
+    });
+
+    scene.add(group);
+
+    group.add(obj3d);
+
+    //
+
+    const geometry = new THREE.SphereGeometry(3, 48, 24);
+
+    for (let i = 0; i < 20; i++) {
+        const material = new THREE.MeshLambertMaterial();
+        material.color.setHSL(Math.random(), 1.0, 0.3);
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 4 - 2;
+        mesh.position.y = Math.random() * 4 - 2;
+        mesh.position.z = Math.random() * 4 - 2;
+        mesh.receiveShadow = true;
+        mesh.castShadow = true;
+        mesh.scale.multiplyScalar(Math.random() * 0.3 + 0.1);
+        group.add(mesh);
+    }
+
+    const floorMaterial = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide });
+
+    const floorGeometry = new THREE.PlaneGeometry(12, 12);
+    const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
+    floorMesh.rotation.x -= Math.PI * 0.5;
+    floorMesh.position.y -= 1.5;
+    group.add(floorMesh);
+    floorMesh.receiveShadow = true;
+
+    const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);
+    const torusMaterial = new THREE.MeshPhongMaterial({ color: 0xffaaff });
+    const torus = new THREE.Mesh(torusGeometry, torusMaterial);
+    torus.position.z = -4;
+    group.add(torus);
+    torus.receiveShadow = true;
+    torus.castShadow = true;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+
+    const renderPass = new RenderPass(scene, camera);
+    composer.addPass(renderPass);
+
+    outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
+    composer.addPass(outlinePass);
+
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/tri_pattern.jpg', function (texture) {
+        outlinePass.patternTexture = texture;
+        texture.wrapS = THREE.RepeatWrapping;
+        texture.wrapT = THREE.RepeatWrapping;
+    });
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    effectFXAA = new ShaderPass(FXAAShader);
+    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
+    composer.addPass(effectFXAA);
+
+    window.addEventListener('resize', onWindowResize);
+
+    renderer.domElement.style.touchAction = 'none';
+    renderer.domElement.addEventListener('pointermove', onPointerMove);
+
+    function onPointerMove(event) {
+        if (event.isPrimary === false) return;
+
+        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+        checkIntersection();
+    }
+
+    function addSelectedObject(object) {
+        selectedObjects = [];
+        selectedObjects.push(object);
+    }
+
+    function checkIntersection() {
+        raycaster.setFromCamera(mouse, camera);
+
+        const intersects = raycaster.intersectObject(scene, true);
+
+        if (intersects.length > 0) {
+            const selectedObject = intersects[0].object;
+            addSelectedObject(selectedObject);
+            outlinePass.selectedObjects = selectedObjects;
+        } else {
+            // outlinePass.selectedObjects = [];
+        }
+    }
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+
+    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
+}
+
+function animate() {
+    stats.begin();
+
+    const timer = performance.now();
+
+    if (params.rotate) {
+        group.rotation.y = timer * 0.0001;
+    }
+
+    controls.update();
+
+    composer.render();
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_pixel.ts b/examples-testing/examples/webgl_postprocessing_pixel.ts
new file mode 100644
index 000000000..15b54d072
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_pixel.ts
@@ -0,0 +1,228 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPixelatedPass } from 'three/addons/postprocessing/RenderPixelatedPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, composer, crystalMesh, clock;
+let gui, params;
+
+init();
+
+function init() {
+    const aspectRatio = window.innerWidth / window.innerHeight;
+
+    camera = new THREE.OrthographicCamera(-aspectRatio, aspectRatio, 1, -1, 0.1, 10);
+    camera.position.y = 2 * Math.tan(Math.PI / 6);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x151729);
+
+    clock = new THREE.Clock();
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.shadowMap.enabled = true;
+    //renderer.setPixelRatio( window.devicePixelRatio );
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    composer = new EffectComposer(renderer);
+    const renderPixelatedPass = new RenderPixelatedPass(6, scene, camera);
+    composer.addPass(renderPixelatedPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxZoom = 2;
+
+    // gui
+
+    gui = new GUI();
+    params = { pixelSize: 6, normalEdgeStrength: 0.3, depthEdgeStrength: 0.4, pixelAlignedPanning: true };
+    gui.add(params, 'pixelSize')
+        .min(1)
+        .max(16)
+        .step(1)
+        .onChange(() => {
+            renderPixelatedPass.setPixelSize(params.pixelSize);
+        });
+    gui.add(renderPixelatedPass, 'normalEdgeStrength').min(0).max(2).step(0.05);
+    gui.add(renderPixelatedPass, 'depthEdgeStrength').min(0).max(1).step(0.05);
+    gui.add(params, 'pixelAlignedPanning');
+
+    // textures
+
+    const loader = new THREE.TextureLoader();
+    const texChecker = pixelTexture(loader.load('textures/checker.png'));
+    const texChecker2 = pixelTexture(loader.load('textures/checker.png'));
+    texChecker.repeat.set(3, 3);
+    texChecker2.repeat.set(1.5, 1.5);
+
+    // meshes
+
+    const boxMaterial = new THREE.MeshPhongMaterial({ map: texChecker2 });
+
+    function addBox(boxSideLength, x, z, rotation) {
+        const mesh = new THREE.Mesh(new THREE.BoxGeometry(boxSideLength, boxSideLength, boxSideLength), boxMaterial);
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+        mesh.rotation.y = rotation;
+        mesh.position.y = boxSideLength / 2;
+        mesh.position.set(x, boxSideLength / 2 + 0.0001, z);
+        scene.add(mesh);
+        return mesh;
+    }
+
+    addBox(0.4, 0, 0, Math.PI / 4);
+    addBox(0.5, -0.5, -0.5, Math.PI / 4);
+
+    const planeSideLength = 2;
+    const planeMesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(planeSideLength, planeSideLength),
+        new THREE.MeshPhongMaterial({ map: texChecker }),
+    );
+    planeMesh.receiveShadow = true;
+    planeMesh.rotation.x = -Math.PI / 2;
+    scene.add(planeMesh);
+
+    const radius = 0.2;
+    const geometry = new THREE.IcosahedronGeometry(radius);
+    crystalMesh = new THREE.Mesh(
+        geometry,
+        new THREE.MeshPhongMaterial({
+            color: 0x68b7e9,
+            emissive: 0x4f7e8b,
+            shininess: 10,
+            specular: 0xffffff,
+        }),
+    );
+    crystalMesh.receiveShadow = true;
+    crystalMesh.castShadow = true;
+    scene.add(crystalMesh);
+
+    // lights
+
+    scene.add(new THREE.AmbientLight(0x757f8e, 3));
+
+    const directionalLight = new THREE.DirectionalLight(0xfffecd, 1.5);
+    directionalLight.position.set(100, 100, 100);
+    directionalLight.castShadow = true;
+    directionalLight.shadow.mapSize.set(2048, 2048);
+    scene.add(directionalLight);
+
+    const spotLight = new THREE.SpotLight(0xffc100, 10, 10, Math.PI / 16, 0.02, 2);
+    spotLight.position.set(2, 2, 0);
+    const target = spotLight.target;
+    scene.add(target);
+    target.position.set(0, 0, 0);
+    spotLight.castShadow = true;
+    scene.add(spotLight);
+}
+
+function onWindowResize() {
+    const aspectRatio = window.innerWidth / window.innerHeight;
+    camera.left = -aspectRatio;
+    camera.right = aspectRatio;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const t = clock.getElapsedTime();
+
+    crystalMesh.material.emissiveIntensity = Math.sin(t * 3) * 0.5 + 0.5;
+    crystalMesh.position.y = 0.7 + Math.sin(t * 2) * 0.05;
+    crystalMesh.rotation.y = stopGoEased(t, 2, 4) * 2 * Math.PI;
+
+    const rendererSize = renderer.getSize(new THREE.Vector2());
+    const aspectRatio = rendererSize.x / rendererSize.y;
+    if (params['pixelAlignedPanning']) {
+        pixelAlignFrustum(
+            camera,
+            aspectRatio,
+            Math.floor(rendererSize.x / params['pixelSize']),
+            Math.floor(rendererSize.y / params['pixelSize']),
+        );
+    } else if (camera.left != -aspectRatio || camera.top != 1.0) {
+        // Reset the Camera Frustum if it has been modified
+        camera.left = -aspectRatio;
+        camera.right = aspectRatio;
+        camera.top = 1.0;
+        camera.bottom = -1.0;
+        camera.updateProjectionMatrix();
+    }
+
+    composer.render();
+}
+
+// Helper functions
+
+function pixelTexture(texture) {
+    texture.minFilter = THREE.NearestFilter;
+    texture.magFilter = THREE.NearestFilter;
+    texture.generateMipmaps = false;
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+    texture.colorSpace = THREE.SRGBColorSpace;
+    return texture;
+}
+
+function easeInOutCubic(x) {
+    return x ** 2 * 3 - x ** 3 * 2;
+}
+
+function linearStep(x, edge0, edge1) {
+    const w = edge1 - edge0;
+    const m = 1 / w;
+    const y0 = -m * edge0;
+    return THREE.MathUtils.clamp(y0 + m * x, 0, 1);
+}
+
+function stopGoEased(x, downtime, period) {
+    const cycle = (x / period) | 0;
+    const tween = x - cycle * period;
+    const linStep = easeInOutCubic(linearStep(tween, downtime, period));
+    return cycle + linStep;
+}
+
+function pixelAlignFrustum(camera, aspectRatio, pixelsPerScreenWidth, pixelsPerScreenHeight) {
+    // 0. Get Pixel Grid Units
+    const worldScreenWidth = (camera.right - camera.left) / camera.zoom;
+    const worldScreenHeight = (camera.top - camera.bottom) / camera.zoom;
+    const pixelWidth = worldScreenWidth / pixelsPerScreenWidth;
+    const pixelHeight = worldScreenHeight / pixelsPerScreenHeight;
+
+    // 1. Project the current camera position along its local rotation bases
+    const camPos = new THREE.Vector3();
+    camera.getWorldPosition(camPos);
+    const camRot = new THREE.Quaternion();
+    camera.getWorldQuaternion(camRot);
+    const camRight = new THREE.Vector3(1.0, 0.0, 0.0).applyQuaternion(camRot);
+    const camUp = new THREE.Vector3(0.0, 1.0, 0.0).applyQuaternion(camRot);
+    const camPosRight = camPos.dot(camRight);
+    const camPosUp = camPos.dot(camUp);
+
+    // 2. Find how far along its position is along these bases in pixel units
+    const camPosRightPx = camPosRight / pixelWidth;
+    const camPosUpPx = camPosUp / pixelHeight;
+
+    // 3. Find the fractional pixel units and convert to world units
+    const fractX = camPosRightPx - Math.round(camPosRightPx);
+    const fractY = camPosUpPx - Math.round(camPosUpPx);
+
+    // 4. Add fractional world units to the left/right top/bottom to align with the pixel grid
+    camera.left = -aspectRatio - fractX * pixelWidth;
+    camera.right = aspectRatio - fractX * pixelWidth;
+    camera.top = 1.0 - fractY * pixelHeight;
+    camera.bottom = -1.0 - fractY * pixelHeight;
+    camera.updateProjectionMatrix();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_procedural.ts b/examples-testing/examples/webgl_postprocessing_procedural.ts
new file mode 100644
index 000000000..869824270
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_procedural.ts
@@ -0,0 +1,77 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let postCamera, postScene, renderer;
+let postMaterial, noiseRandom1DMaterial, noiseRandom2DMaterial, noiseRandom3DMaterial, postQuad;
+let stats;
+
+const params = { procedure: 'noiseRandom3D' };
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // Setup post processing stage
+    postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
+    noiseRandom1DMaterial = new THREE.ShaderMaterial({
+        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
+        fragmentShader: document.querySelector('#noiseRandom1D-frag').textContent.trim(),
+    });
+    noiseRandom2DMaterial = new THREE.ShaderMaterial({
+        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
+        fragmentShader: document.querySelector('#noiseRandom2D-frag').textContent.trim(),
+    });
+    noiseRandom3DMaterial = new THREE.ShaderMaterial({
+        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
+        fragmentShader: document.querySelector('#noiseRandom3D-frag').textContent.trim(),
+    });
+    postMaterial = noiseRandom3DMaterial;
+    const postPlane = new THREE.PlaneGeometry(2, 2);
+    postQuad = new THREE.Mesh(postPlane, postMaterial);
+    postScene = new THREE.Scene();
+    postScene.add(postQuad);
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'procedure', ['noiseRandom1D', 'noiseRandom2D', 'noiseRandom3D']);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    switch (params.procedure) {
+        case 'noiseRandom1D':
+            postMaterial = noiseRandom1DMaterial;
+            break;
+        case 'noiseRandom2D':
+            postMaterial = noiseRandom2DMaterial;
+            break;
+        case 'noiseRandom3D':
+            postMaterial = noiseRandom3DMaterial;
+            break;
+    }
+
+    postQuad.material = postMaterial;
+
+    // render post FX
+    renderer.render(postScene, postCamera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts b/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
new file mode 100644
index 000000000..fa46d4c8d
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
@@ -0,0 +1,167 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { HalftonePass } from 'three/addons/postprocessing/HalftonePass.js';
+
+let renderer, clock, camera, stats;
+
+const rotationSpeed = Math.PI / 64;
+
+let composer, group;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+
+    clock = new THREE.Clock();
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 12;
+
+    stats = new Stats();
+
+    document.body.appendChild(renderer.domElement);
+    document.body.appendChild(stats.dom);
+
+    // camera controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0, 0);
+    controls.update();
+
+    // scene
+
+    const scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x444444);
+
+    group = new THREE.Group();
+    const floor = new THREE.Mesh(new THREE.BoxGeometry(100, 1, 100), new THREE.MeshPhongMaterial({}));
+    floor.position.y = -10;
+    const light = new THREE.PointLight(0xffffff, 250);
+    light.position.y = 2;
+    group.add(floor, light);
+    scene.add(group);
+
+    const mat = new THREE.ShaderMaterial({
+        uniforms: {},
+
+        vertexShader: [
+            'varying vec2 vUV;',
+            'varying vec3 vNormal;',
+
+            'void main() {',
+
+            'vUV = uv;',
+            'vNormal = vec3( normal );',
+            'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+
+            '}',
+        ].join('\n'),
+
+        fragmentShader: [
+            'varying vec2 vUV;',
+            'varying vec3 vNormal;',
+
+            'void main() {',
+
+            'vec4 c = vec4( abs( vNormal ) + vec3( vUV, 0.0 ), 0.0 );',
+            'gl_FragColor = c;',
+
+            '}',
+        ].join('\n'),
+    });
+
+    for (let i = 0; i < 50; ++i) {
+        // fill scene with coloured cubes
+        const mesh = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), mat);
+        mesh.position.set(Math.random() * 16 - 8, Math.random() * 16 - 8, Math.random() * 16 - 8);
+        mesh.rotation.set(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
+        group.add(mesh);
+    }
+
+    // post-processing
+
+    composer = new EffectComposer(renderer);
+    const renderPass = new RenderPass(scene, camera);
+    const params = {
+        shape: 1,
+        radius: 4,
+        rotateR: Math.PI / 12,
+        rotateB: (Math.PI / 12) * 2,
+        rotateG: (Math.PI / 12) * 3,
+        scatter: 0,
+        blending: 1,
+        blendingMode: 1,
+        greyscale: false,
+        disable: false,
+    };
+    const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, params);
+    composer.addPass(renderPass);
+    composer.addPass(halftonePass);
+
+    window.onresize = function () {
+        // resize composer
+        renderer.setSize(window.innerWidth, window.innerHeight);
+        composer.setSize(window.innerWidth, window.innerHeight);
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+    };
+
+    // GUI
+
+    const controller = {
+        radius: halftonePass.uniforms['radius'].value,
+        rotateR: halftonePass.uniforms['rotateR'].value / (Math.PI / 180),
+        rotateG: halftonePass.uniforms['rotateG'].value / (Math.PI / 180),
+        rotateB: halftonePass.uniforms['rotateB'].value / (Math.PI / 180),
+        scatter: halftonePass.uniforms['scatter'].value,
+        shape: halftonePass.uniforms['shape'].value,
+        greyscale: halftonePass.uniforms['greyscale'].value,
+        blending: halftonePass.uniforms['blending'].value,
+        blendingMode: halftonePass.uniforms['blendingMode'].value,
+        disable: halftonePass.uniforms['disable'].value,
+    };
+
+    function onGUIChange() {
+        // update uniforms
+        halftonePass.uniforms['radius'].value = controller.radius;
+        halftonePass.uniforms['rotateR'].value = controller.rotateR * (Math.PI / 180);
+        halftonePass.uniforms['rotateG'].value = controller.rotateG * (Math.PI / 180);
+        halftonePass.uniforms['rotateB'].value = controller.rotateB * (Math.PI / 180);
+        halftonePass.uniforms['scatter'].value = controller.scatter;
+        halftonePass.uniforms['shape'].value = controller.shape;
+        halftonePass.uniforms['greyscale'].value = controller.greyscale;
+        halftonePass.uniforms['blending'].value = controller.blending;
+        halftonePass.uniforms['blendingMode'].value = controller.blendingMode;
+        halftonePass.uniforms['disable'].value = controller.disable;
+    }
+
+    const gui = new GUI();
+    gui.add(controller, 'shape', { Dot: 1, Ellipse: 2, Line: 3, Square: 4 }).onChange(onGUIChange);
+    gui.add(controller, 'radius', 1, 25).onChange(onGUIChange);
+    gui.add(controller, 'rotateR', 0, 90).onChange(onGUIChange);
+    gui.add(controller, 'rotateG', 0, 90).onChange(onGUIChange);
+    gui.add(controller, 'rotateB', 0, 90).onChange(onGUIChange);
+    gui.add(controller, 'scatter', 0, 1, 0.01).onChange(onGUIChange);
+    gui.add(controller, 'greyscale').onChange(onGUIChange);
+    gui.add(controller, 'blending', 0, 1, 0.01).onChange(onGUIChange);
+    gui.add(controller, 'blendingMode', { Linear: 1, Multiply: 2, Add: 3, Lighter: 4, Darker: 5 }).onChange(
+        onGUIChange,
+    );
+    gui.add(controller, 'disable').onChange(onGUIChange);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+    stats.update();
+    group.rotation.y += delta * rotationSpeed;
+    composer.render(delta);
+}
diff --git a/examples-testing/examples/webgl_postprocessing_sao.ts b/examples-testing/examples/webgl_postprocessing_sao.ts
new file mode 100644
index 000000000..bf40d026b
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_sao.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { SAOPass } from 'three/addons/postprocessing/SAOPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let container, stats;
+let camera, scene, renderer;
+let composer, renderPass, saoPass;
+let group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(65, width / height, 3, 10);
+    camera.position.z = 7;
+
+    scene = new THREE.Scene();
+
+    group = new THREE.Object3D();
+    scene.add(group);
+
+    const light = new THREE.PointLight(0xefffef, 500);
+    light.position.z = 10;
+    light.position.y = -10;
+    light.position.x = -10;
+    scene.add(light);
+
+    const light2 = new THREE.PointLight(0xffefef, 500);
+    light2.position.z = 10;
+    light2.position.x = -10;
+    light2.position.y = 10;
+    scene.add(light2);
+
+    const light3 = new THREE.PointLight(0xefefff, 500);
+    light3.position.z = 10;
+    light3.position.x = 10;
+    light3.position.y = -10;
+    scene.add(light3);
+
+    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
+    scene.add(light4);
+
+    const geometry = new THREE.SphereGeometry(3, 48, 24);
+
+    for (let i = 0; i < 120; i++) {
+        const material = new THREE.MeshStandardMaterial();
+        material.roughness = 0.5 * Math.random() + 0.25;
+        material.metalness = 0;
+        material.color.setHSL(Math.random(), 1.0, 0.3);
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 4 - 2;
+        mesh.position.y = Math.random() * 4 - 2;
+        mesh.position.z = Math.random() * 4 - 2;
+        mesh.rotation.x = Math.random();
+        mesh.rotation.y = Math.random();
+        mesh.rotation.z = Math.random();
+
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 0.2 + 0.05;
+        group.add(mesh);
+    }
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    composer = new EffectComposer(renderer);
+    renderPass = new RenderPass(scene, camera);
+    composer.addPass(renderPass);
+    saoPass = new SAOPass(scene, camera);
+    composer.addPass(saoPass);
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    // Init gui
+    const gui = new GUI();
+    gui.add(saoPass.params, 'output', {
+        Default: SAOPass.OUTPUT.Default,
+        'SAO Only': SAOPass.OUTPUT.SAO,
+        Normal: SAOPass.OUTPUT.Normal,
+    }).onChange(function (value) {
+        saoPass.params.output = value;
+    });
+    gui.add(saoPass.params, 'saoBias', -1, 1);
+    gui.add(saoPass.params, 'saoIntensity', 0, 1);
+    gui.add(saoPass.params, 'saoScale', 0, 10);
+    gui.add(saoPass.params, 'saoKernelRadius', 1, 100);
+    gui.add(saoPass.params, 'saoMinResolution', 0, 1);
+    gui.add(saoPass.params, 'saoBlur');
+    gui.add(saoPass.params, 'saoBlurRadius', 0, 200);
+    gui.add(saoPass.params, 'saoBlurStdDev', 0.5, 150);
+    gui.add(saoPass.params, 'saoBlurDepthCutoff', 0.0, 0.1);
+    gui.add(saoPass, 'enabled');
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth || 1;
+    const height = window.innerHeight || 1;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+    renderer.setSize(width, height);
+
+    composer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    const timer = performance.now();
+    group.rotation.x = timer * 0.0002;
+    group.rotation.y = timer * 0.0001;
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_smaa.ts b/examples-testing/examples/webgl_postprocessing_smaa.ts
new file mode 100644
index 000000000..6f71f6478
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_smaa.ts
@@ -0,0 +1,109 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, composer, stats, smaaPass;
+
+const params = {
+    enabled: true,
+    autoRotate: true,
+};
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.BoxGeometry(120, 120, 120);
+    const material1 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
+
+    const mesh1 = new THREE.Mesh(geometry, material1);
+    mesh1.position.x = -100;
+    scene.add(mesh1);
+
+    const texture = new THREE.TextureLoader().load('textures/brick_diffuse.jpg');
+    texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const material2 = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh2 = new THREE.Mesh(geometry, material2);
+    mesh2.position.x = 100;
+    scene.add(mesh2);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    composer.addPass(new RenderPass(scene, camera));
+
+    smaaPass = new SMAAPass(
+        window.innerWidth * renderer.getPixelRatio(),
+        window.innerHeight * renderer.getPixelRatio(),
+    );
+    composer.addPass(smaaPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+
+    const gui = new GUI();
+
+    const smaaFolder = gui.addFolder('SMAA');
+    smaaFolder.add(params, 'enabled');
+
+    const sceneFolder = gui.addFolder('Scene');
+    sceneFolder.add(params, 'autoRotate');
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+
+    if (params.autoRotate === true) {
+        for (let i = 0; i < scene.children.length; i++) {
+            const child = scene.children[i];
+
+            child.rotation.x += 0.005;
+            child.rotation.y += 0.01;
+        }
+    }
+
+    smaaPass.enabled = params.enabled;
+
+    composer.render();
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_sobel.ts b/examples-testing/examples/webgl_postprocessing_sobel.ts
new file mode 100644
index 000000000..55d88dc02
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_sobel.ts
@@ -0,0 +1,111 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+
+import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
+import { SobelOperatorShader } from 'three/addons/shaders/SobelOperatorShader.js';
+
+let camera, scene, renderer, composer;
+
+let effectSobel;
+
+const params = {
+    enable: true,
+};
+
+init();
+
+function init() {
+    //
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 1, 3);
+    camera.lookAt(scene.position);
+
+    //
+
+    const geometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 32);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffff00 });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xe7e7e7);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 20);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    const renderPass = new RenderPass(scene, camera);
+    composer.addPass(renderPass);
+
+    // color to grayscale conversion
+
+    const effectGrayScale = new ShaderPass(LuminosityShader);
+    composer.addPass(effectGrayScale);
+
+    // you might want to use a gaussian blur filter before
+    // the next pass to improve the result of the Sobel operator
+
+    // Sobel operator
+
+    effectSobel = new ShaderPass(SobelOperatorShader);
+    effectSobel.uniforms['resolution'].value.x = window.innerWidth * window.devicePixelRatio;
+    effectSobel.uniforms['resolution'].value.y = window.innerHeight * window.devicePixelRatio;
+    composer.addPass(effectSobel);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'enable');
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+
+    effectSobel.uniforms['resolution'].value.x = window.innerWidth * window.devicePixelRatio;
+    effectSobel.uniforms['resolution'].value.y = window.innerHeight * window.devicePixelRatio;
+}
+
+function animate() {
+    if (params.enable === true) {
+        composer.render();
+    } else {
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgl_postprocessing_ssaa.ts b/examples-testing/examples/webgl_postprocessing_ssaa.ts
new file mode 100644
index 000000000..429e02dee
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_ssaa.ts
@@ -0,0 +1,206 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { SSAARenderPass } from 'three/addons/postprocessing/SSAARenderPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let scene, renderer, composer;
+let cameraP, ssaaRenderPassP;
+let cameraO, ssaaRenderPassO;
+let gui, stats;
+
+const params = {
+    sampleLevel: 4,
+    unbiased: true,
+    camera: 'perspective',
+    clearColor: 'black',
+    clearAlpha: 1.0,
+    viewOffsetX: 0,
+    autoRotate: true,
+};
+
+init();
+
+clearGui();
+
+function clearGui() {
+    if (gui) gui.destroy();
+
+    gui = new GUI();
+
+    gui.add(params, 'unbiased');
+    gui.add(params, 'sampleLevel', {
+        'Level 0: 1 Sample': 0,
+        'Level 1: 2 Samples': 1,
+        'Level 2: 4 Samples': 2,
+        'Level 3: 8 Samples': 3,
+        'Level 4: 16 Samples': 4,
+        'Level 5: 32 Samples': 5,
+    });
+    gui.add(params, 'camera', ['perspective', 'orthographic']);
+    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
+    gui.add(params, 'clearAlpha', 0, 1);
+    gui.add(params, 'viewOffsetX', -100, 100);
+    gui.add(params, 'autoRotate');
+
+    gui.open();
+}
+
+function init() {
+    const container = document.getElementById('container');
+
+    const width = window.innerWidth || 1;
+    const height = window.innerHeight || 1;
+    const aspect = width / height;
+    const devicePixelRatio = window.devicePixelRatio || 1;
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    cameraP = new THREE.PerspectiveCamera(65, aspect, 3, 10);
+    cameraP.position.z = 7;
+    cameraP.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
+
+    cameraO = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 3, 10);
+    cameraO.position.z = 7;
+
+    const fov = THREE.MathUtils.degToRad(cameraP.fov);
+    const hyperfocus = (cameraP.near + cameraP.far) / 2;
+    const _height = 2 * Math.tan(fov / 2) * hyperfocus;
+    cameraO.zoom = height / _height;
+
+    scene = new THREE.Scene();
+
+    const group = new THREE.Group();
+    scene.add(group);
+
+    const light = new THREE.PointLight(0xefffef, 500);
+    light.position.z = 10;
+    light.position.y = -10;
+    light.position.x = -10;
+    scene.add(light);
+
+    const light2 = new THREE.PointLight(0xffefef, 500);
+    light2.position.z = 10;
+    light2.position.x = -10;
+    light2.position.y = 10;
+    scene.add(light2);
+
+    const light3 = new THREE.PointLight(0xefefff, 500);
+    light3.position.z = 10;
+    light3.position.x = 10;
+    light3.position.y = -10;
+    scene.add(light3);
+
+    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
+    scene.add(light4);
+
+    const geometry = new THREE.SphereGeometry(3, 48, 24);
+
+    for (let i = 0; i < 120; i++) {
+        const material = new THREE.MeshStandardMaterial();
+        material.roughness = 0.5 * Math.random() + 0.25;
+        material.metalness = 0;
+        material.color.setHSL(Math.random(), 1.0, 0.3);
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 4 - 2;
+        mesh.position.y = Math.random() * 4 - 2;
+        mesh.position.z = Math.random() * 4 - 2;
+        mesh.rotation.x = Math.random();
+        mesh.rotation.y = Math.random();
+        mesh.rotation.z = Math.random();
+
+        mesh.scale.setScalar(Math.random() * 0.2 + 0.05);
+        group.add(mesh);
+    }
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+    composer.setPixelRatio(1); // ensure pixel ratio is always 1 for performance reasons
+    ssaaRenderPassP = new SSAARenderPass(scene, cameraP);
+    composer.addPass(ssaaRenderPassP);
+    ssaaRenderPassO = new SSAARenderPass(scene, cameraO);
+    composer.addPass(ssaaRenderPassO);
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    const aspect = width / height;
+
+    cameraP.aspect = aspect;
+    cameraP.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
+    cameraO.updateProjectionMatrix();
+
+    cameraO.left = -height * aspect;
+    cameraO.right = height * aspect;
+    cameraO.top = height;
+    cameraO.bottom = -height;
+    cameraO.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+
+    if (params.autoRotate) {
+        for (let i = 0; i < scene.children.length; i++) {
+            const child = scene.children[i];
+
+            child.rotation.x += 0.005;
+            child.rotation.y += 0.01;
+        }
+    }
+
+    let newColor = ssaaRenderPassP.clearColor;
+
+    switch (params.clearColor) {
+        case 'blue':
+            newColor = 0x0000ff;
+            break;
+        case 'red':
+            newColor = 0xff0000;
+            break;
+        case 'green':
+            newColor = 0x00ff00;
+            break;
+        case 'white':
+            newColor = 0xffffff;
+            break;
+        case 'black':
+            newColor = 0x000000;
+            break;
+    }
+
+    ssaaRenderPassP.clearColor = ssaaRenderPassO.clearColor = newColor;
+    ssaaRenderPassP.clearAlpha = ssaaRenderPassO.clearAlpha = params.clearAlpha;
+
+    ssaaRenderPassP.sampleLevel = ssaaRenderPassO.sampleLevel = params.sampleLevel;
+    ssaaRenderPassP.unbiased = ssaaRenderPassO.unbiased = params.unbiased;
+
+    ssaaRenderPassP.enabled = params.camera === 'perspective';
+    ssaaRenderPassO.enabled = params.camera === 'orthographic';
+
+    cameraP.view.offsetX = params.viewOffsetX;
+
+    composer.render();
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_ssao.ts b/examples-testing/examples/webgl_postprocessing_ssao.ts
new file mode 100644
index 000000000..e55ab0446
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_ssao.ts
@@ -0,0 +1,118 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let container, stats;
+let camera, scene, renderer;
+let composer;
+let group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 100, 700);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xaaaaaa);
+
+    scene.add(new THREE.DirectionalLight(0xffffff, 4));
+    scene.add(new THREE.AmbientLight(0xffffff));
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const geometry = new THREE.BoxGeometry(10, 10, 10);
+
+    for (let i = 0; i < 100; i++) {
+        const material = new THREE.MeshLambertMaterial({
+            color: Math.random() * 0xffffff,
+        });
+
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.x = Math.random() * 400 - 200;
+        mesh.position.y = Math.random() * 400 - 200;
+        mesh.position.z = Math.random() * 400 - 200;
+        mesh.rotation.x = Math.random();
+        mesh.rotation.y = Math.random();
+        mesh.rotation.z = Math.random();
+
+        mesh.scale.setScalar(Math.random() * 10 + 2);
+        group.add(mesh);
+    }
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    composer = new EffectComposer(renderer);
+
+    const renderPass = new RenderPass(scene, camera);
+    composer.addPass(renderPass);
+
+    const ssaoPass = new SSAOPass(scene, camera, width, height);
+    composer.addPass(ssaoPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    // Init gui
+    const gui = new GUI();
+
+    gui.add(ssaoPass, 'output', {
+        Default: SSAOPass.OUTPUT.Default,
+        'SSAO Only': SSAOPass.OUTPUT.SSAO,
+        'SSAO Only + Blur': SSAOPass.OUTPUT.Blur,
+        Depth: SSAOPass.OUTPUT.Depth,
+        Normal: SSAOPass.OUTPUT.Normal,
+    }).onChange(function (value) {
+        ssaoPass.output = value;
+    });
+    gui.add(ssaoPass, 'kernelRadius').min(0).max(32);
+    gui.add(ssaoPass, 'minDistance').min(0.001).max(0.02);
+    gui.add(ssaoPass, 'maxDistance').min(0.01).max(0.3);
+    gui.add(ssaoPass, 'enabled');
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    const timer = performance.now();
+    group.rotation.x = timer * 0.0002;
+    group.rotation.y = timer * 0.0001;
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_ssr.ts b/examples-testing/examples/webgl_postprocessing_ssr.ts
new file mode 100644
index 000000000..307cfd1de
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_ssr.ts
@@ -0,0 +1,261 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { SSRPass } from 'three/addons/postprocessing/SSRPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+import { ReflectorForSSRPass } from 'three/addons/objects/ReflectorForSSRPass.js';
+
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+const params = {
+    enableSSR: true,
+    autoRotate: true,
+    otherMeshes: true,
+    groundReflector: true,
+};
+let composer;
+let ssrPass;
+let gui;
+let stats;
+let controls;
+let camera, scene, renderer;
+const otherMeshes = [];
+let groundReflector;
+const selects = [];
+
+const container = document.querySelector('#container');
+
+// Configure and create Draco decoder.
+const dracoLoader = new DRACOLoader();
+dracoLoader.setDecoderPath('jsm/libs/draco/');
+dracoLoader.setDecoderConfig({ type: 'js' });
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);
+    camera.position.set(0.13271600513224902, 0.3489546826045913, 0.43921296427927076);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x443333);
+    scene.fog = new THREE.Fog(0x443333, 1, 4);
+
+    // Ground
+    const plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), new THREE.MeshPhongMaterial({ color: 0xcbcbcb }));
+    plane.rotation.x = -Math.PI / 2;
+    plane.position.y = -0.0001;
+    // plane.receiveShadow = true;
+    scene.add(plane);
+
+    // Lights
+    const hemiLight = new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3);
+    scene.add(hemiLight);
+
+    const spotLight = new THREE.SpotLight();
+    spotLight.intensity = 8;
+    spotLight.angle = Math.PI / 16;
+    spotLight.penumbra = 0.5;
+    // spotLight.castShadow = true;
+    spotLight.position.set(-1, 1, 1);
+    scene.add(spotLight);
+
+    dracoLoader.load('models/draco/bunny.drc', function (geometry) {
+        geometry.computeVertexNormals();
+
+        const material = new THREE.MeshStandardMaterial({ color: 0xa5a5a5 });
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.y = -0.0365;
+        scene.add(mesh);
+        selects.push(mesh);
+
+        // Release decoder resources.
+        dracoLoader.dispose();
+    });
+
+    let geometry, material, mesh;
+
+    geometry = new THREE.BoxGeometry(0.05, 0.05, 0.05);
+    material = new THREE.MeshStandardMaterial({ color: 'green' });
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.position.set(-0.12, 0.025, 0.015);
+    scene.add(mesh);
+    otherMeshes.push(mesh);
+    selects.push(mesh);
+
+    geometry = new THREE.IcosahedronGeometry(0.025, 4);
+    material = new THREE.MeshStandardMaterial({ color: 'cyan' });
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.position.set(-0.05, 0.025, 0.08);
+    scene.add(mesh);
+    otherMeshes.push(mesh);
+    selects.push(mesh);
+
+    geometry = new THREE.ConeGeometry(0.025, 0.05, 64);
+    material = new THREE.MeshStandardMaterial({ color: 'yellow' });
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.position.set(-0.05, 0.025, -0.055);
+    scene.add(mesh);
+    otherMeshes.push(mesh);
+    selects.push(mesh);
+
+    geometry = new THREE.PlaneGeometry(1, 1);
+    groundReflector = new ReflectorForSSRPass(geometry, {
+        clipBias: 0.0003,
+        textureWidth: window.innerWidth,
+        textureHeight: window.innerHeight,
+        color: 0x888888,
+        useDepthTexture: true,
+    });
+    groundReflector.material.depthWrite = false;
+    groundReflector.rotation.x = -Math.PI / 2;
+    groundReflector.visible = false;
+    scene.add(groundReflector);
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: false });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.target.set(0, 0.0635, 0);
+    controls.update();
+    controls.enabled = !params.autoRotate;
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // composer
+
+    composer = new EffectComposer(renderer);
+    ssrPass = new SSRPass({
+        renderer,
+        scene,
+        camera,
+        width: innerWidth,
+        height: innerHeight,
+        groundReflector: params.groundReflector ? groundReflector : null,
+        selects: params.groundReflector ? selects : null,
+    });
+
+    composer.addPass(ssrPass);
+    composer.addPass(new OutputPass());
+
+    // GUI
+
+    gui = new GUI({ width: 260 });
+    gui.add(params, 'enableSSR').name('Enable SSR');
+    gui.add(params, 'groundReflector').onChange(() => {
+        if (params.groundReflector) {
+            (ssrPass.groundReflector = groundReflector), (ssrPass.selects = selects);
+        } else {
+            (ssrPass.groundReflector = null), (ssrPass.selects = null);
+        }
+    });
+    ssrPass.thickness = 0.018;
+    gui.add(ssrPass, 'thickness').min(0).max(0.1).step(0.0001);
+    ssrPass.infiniteThick = false;
+    gui.add(ssrPass, 'infiniteThick');
+    gui.add(params, 'autoRotate').onChange(() => {
+        controls.enabled = !params.autoRotate;
+    });
+
+    const folder = gui.addFolder('more settings');
+    folder.add(ssrPass, 'fresnel').onChange(() => {
+        groundReflector.fresnel = ssrPass.fresnel;
+    });
+    folder.add(ssrPass, 'distanceAttenuation').onChange(() => {
+        groundReflector.distanceAttenuation = ssrPass.distanceAttenuation;
+    });
+    ssrPass.maxDistance = 0.1;
+    groundReflector.maxDistance = ssrPass.maxDistance;
+    folder
+        .add(ssrPass, 'maxDistance')
+        .min(0)
+        .max(0.5)
+        .step(0.001)
+        .onChange(() => {
+            groundReflector.maxDistance = ssrPass.maxDistance;
+        });
+    folder.add(params, 'otherMeshes').onChange(() => {
+        if (params.otherMeshes) {
+            otherMeshes.forEach(mesh => (mesh.visible = true));
+        } else {
+            otherMeshes.forEach(mesh => (mesh.visible = false));
+        }
+    });
+    folder.add(ssrPass, 'bouncing');
+    folder
+        .add(ssrPass, 'output', {
+            Default: SSRPass.OUTPUT.Default,
+            'SSR Only': SSRPass.OUTPUT.SSR,
+            Beauty: SSRPass.OUTPUT.Beauty,
+            Depth: SSRPass.OUTPUT.Depth,
+            Normal: SSRPass.OUTPUT.Normal,
+            Metalness: SSRPass.OUTPUT.Metalness,
+        })
+        .onChange(function (value) {
+            ssrPass.output = value;
+        });
+    ssrPass.opacity = 1;
+    groundReflector.opacity = ssrPass.opacity;
+    folder
+        .add(ssrPass, 'opacity')
+        .min(0)
+        .max(1)
+        .onChange(() => {
+            groundReflector.opacity = ssrPass.opacity;
+        });
+    folder.add(ssrPass, 'blur');
+    // folder.open()
+    // gui.close()
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+    groundReflector.getRenderTarget().setSize(window.innerWidth, window.innerHeight);
+    groundReflector.resolution.set(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    if (params.autoRotate) {
+        const timer = Date.now() * 0.0003;
+
+        camera.position.x = Math.sin(timer) * 0.5;
+        camera.position.y = 0.2135;
+        camera.position.z = Math.cos(timer) * 0.5;
+        camera.lookAt(0, 0.0635, 0);
+    } else {
+        controls.update();
+    }
+
+    if (params.enableSSR) {
+        // TODO: groundReflector has full ground info, need use it to solve reflection gaps problem on objects when camera near ground.
+        // TODO: the normal and depth info where groundReflector reflected need to be changed.
+        composer.render();
+    } else {
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgl_postprocessing_taa.ts b/examples-testing/examples/webgl_postprocessing_taa.ts
new file mode 100644
index 000000000..11a986741
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_taa.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, scene, renderer, composer, taaRenderPass, renderPass;
+let gui, stats;
+let index = 0;
+
+const param = { TAAEnabled: '1', TAASampleLevel: 0 };
+
+init();
+
+clearGui();
+
+function clearGui() {
+    if (gui) gui.destroy();
+
+    gui = new GUI();
+
+    gui.add(param, 'TAAEnabled', {
+        Disabled: '0',
+        Enabled: '1',
+    }).onFinishChange(function () {
+        if (taaRenderPass) {
+            taaRenderPass.enabled = param.TAAEnabled === '1';
+            renderPass.enabled = param.TAAEnabled !== '1';
+        }
+    });
+
+    gui.add(param, 'TAASampleLevel', {
+        'Level 0: 1 Sample': 0,
+        'Level 1: 2 Samples': 1,
+        'Level 2: 4 Samples': 2,
+        'Level 3: 8 Samples': 3,
+        'Level 4: 16 Samples': 4,
+        'Level 5: 32 Samples': 5,
+    }).onFinishChange(function () {
+        if (taaRenderPass) {
+            taaRenderPass.sampleLevel = param.TAASampleLevel;
+        }
+    });
+
+    gui.open();
+}
+
+function init() {
+    const container = document.getElementById('container');
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 300;
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.BoxGeometry(120, 120, 120);
+    const material1 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
+
+    const mesh1 = new THREE.Mesh(geometry, material1);
+    mesh1.position.x = -100;
+    scene.add(mesh1);
+
+    const texture = new THREE.TextureLoader().load('textures/brick_diffuse.jpg');
+    texture.minFilter = THREE.NearestFilter;
+    texture.magFilter = THREE.NearestFilter;
+    texture.anisotropy = 1;
+    texture.generateMipmaps = false;
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const material2 = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh2 = new THREE.Mesh(geometry, material2);
+    mesh2.position.x = 100;
+    scene.add(mesh2);
+
+    // postprocessing
+
+    composer = new EffectComposer(renderer);
+
+    taaRenderPass = new TAARenderPass(scene, camera);
+    taaRenderPass.unbiased = false;
+    composer.addPass(taaRenderPass);
+
+    renderPass = new RenderPass(scene, camera);
+    renderPass.enabled = false;
+    composer.addPass(renderPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    index++;
+
+    if (Math.round(index / 200) % 2 === 0) {
+        for (let i = 0; i < scene.children.length; i++) {
+            const child = scene.children[i];
+
+            child.rotation.x += 0.005;
+            child.rotation.y += 0.01;
+        }
+
+        if (taaRenderPass) taaRenderPass.accumulate = false;
+    } else {
+        if (taaRenderPass) taaRenderPass.accumulate = true;
+    }
+
+    composer.render();
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_transition.ts b/examples-testing/examples/webgl_postprocessing_transition.ts
new file mode 100644
index 000000000..d05466131
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_transition.ts
@@ -0,0 +1,211 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import TWEEN from 'three/addons/libs/tween.module.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderTransitionPass } from 'three/addons/postprocessing/RenderTransitionPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let stats;
+let renderer, composer, renderTransitionPass;
+
+const textures = [];
+const clock = new THREE.Clock();
+
+const params = {
+    sceneAnimate: true,
+    transitionAnimate: true,
+    transition: 0,
+    useTexture: true,
+    texture: 5,
+    cycle: true,
+    threshold: 0.1,
+};
+
+const fxSceneA = new FXScene(new THREE.BoxGeometry(2, 2, 2), new THREE.Vector3(0, -0.4, 0), 0xffffff);
+const fxSceneB = new FXScene(new THREE.IcosahedronGeometry(1, 1), new THREE.Vector3(0, 0.2, 0.1), 0x000000);
+
+init();
+
+function init() {
+    initGUI();
+    initTextures();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    composer = new EffectComposer(renderer);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    renderTransitionPass = new RenderTransitionPass(fxSceneA.scene, fxSceneA.camera, fxSceneB.scene, fxSceneB.camera);
+    renderTransitionPass.setTexture(textures[0]);
+    composer.addPass(renderTransitionPass);
+
+    const outputPass = new OutputPass();
+    composer.addPass(outputPass);
+}
+
+window.addEventListener('resize', onWindowResize);
+
+function onWindowResize() {
+    fxSceneA.resize();
+    fxSceneB.resize();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+new TWEEN.Tween(params)
+    .to({ transition: 1 }, 1500)
+    .onUpdate(function () {
+        renderTransitionPass.setTransition(params.transition);
+
+        // Change the current alpha texture after each transition
+        if (params.cycle) {
+            if (params.transition == 0 || params.transition == 1) {
+                params.texture = (params.texture + 1) % textures.length;
+                renderTransitionPass.setTexture(textures[params.texture]);
+            }
+        }
+    })
+    .repeat(Infinity)
+    .delay(2000)
+    .yoyo(true)
+    .start();
+
+function animate() {
+    // Transition animation
+    if (params.transitionAnimate) TWEEN.update();
+
+    const delta = clock.getDelta();
+    fxSceneA.update(delta);
+    fxSceneB.update(delta);
+
+    render();
+    stats.update();
+}
+
+function initTextures() {
+    const loader = new THREE.TextureLoader();
+
+    for (let i = 0; i < 6; i++) {
+        textures[i] = loader.load('textures/transition/transition' + (i + 1) + '.png');
+    }
+}
+
+function initGUI() {
+    const gui = new GUI();
+
+    gui.add(params, 'sceneAnimate').name('Animate scene');
+    gui.add(params, 'transitionAnimate').name('Animate transition');
+    gui.add(params, 'transition', 0, 1, 0.01)
+        .onChange(function (value) {
+            renderTransitionPass.setTransition(value);
+        })
+        .listen();
+
+    gui.add(params, 'useTexture').onChange(function (value) {
+        renderTransitionPass.useTexture(value);
+    });
+
+    gui.add(params, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 })
+        .onChange(function (value) {
+            renderTransitionPass.setTexture(textures[value]);
+        })
+        .listen();
+
+    gui.add(params, 'cycle');
+
+    gui.add(params, 'threshold', 0, 1, 0.01).onChange(function (value) {
+        renderTransitionPass.setTextureThreshold(value);
+    });
+}
+
+function render() {
+    // Prevent render both scenes when it's not necessary
+    if (params.transition === 0) {
+        renderer.render(fxSceneB.scene, fxSceneB.camera);
+    } else if (params.transition === 1) {
+        renderer.render(fxSceneA.scene, fxSceneA.camera);
+    } else {
+        // When 0 < transition < 1 render transition between two scenes
+        composer.render();
+    }
+}
+
+function FXScene(geometry, rotationSpeed, backgroundColor) {
+    const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 20;
+
+    // Setup scene
+    const scene = new THREE.Scene();
+    scene.background = new THREE.Color(backgroundColor);
+    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 1, 4);
+    scene.add(light);
+
+    this.rotationSpeed = rotationSpeed;
+
+    const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
+    const material = new THREE.MeshPhongMaterial({ color: color, flatShading: true });
+    const mesh = generateInstancedMesh(geometry, material, 500);
+    scene.add(mesh);
+
+    this.scene = scene;
+    this.camera = camera;
+    this.mesh = mesh;
+
+    this.update = function (delta) {
+        if (params.sceneAnimate) {
+            mesh.rotation.x += this.rotationSpeed.x * delta;
+            mesh.rotation.y += this.rotationSpeed.y * delta;
+            mesh.rotation.z += this.rotationSpeed.z * delta;
+        }
+    };
+
+    this.resize = function () {
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+    };
+}
+
+function generateInstancedMesh(geometry, material, count) {
+    const mesh = new THREE.InstancedMesh(geometry, material, count);
+
+    const dummy = new THREE.Object3D();
+    const color = new THREE.Color();
+
+    for (let i = 0; i < count; i++) {
+        dummy.position.x = Math.random() * 100 - 50;
+        dummy.position.y = Math.random() * 60 - 30;
+        dummy.position.z = Math.random() * 80 - 40;
+
+        dummy.rotation.x = Math.random() * 2 * Math.PI;
+        dummy.rotation.y = Math.random() * 2 * Math.PI;
+        dummy.rotation.z = Math.random() * 2 * Math.PI;
+
+        dummy.scale.x = Math.random() * 2 + 1;
+
+        if (geometry.type === 'BoxGeometry') {
+            dummy.scale.y = Math.random() * 2 + 1;
+            dummy.scale.z = Math.random() * 2 + 1;
+        } else {
+            dummy.scale.y = dummy.scale.x;
+            dummy.scale.z = dummy.scale.x;
+        }
+
+        dummy.updateMatrix();
+
+        mesh.setMatrixAt(i, dummy.matrix);
+        mesh.setColorAt(i, color.setScalar(0.1 + 0.9 * Math.random()));
+    }
+
+    return mesh;
+}
diff --git a/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts b/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
new file mode 100644
index 000000000..53ec2fe2f
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
@@ -0,0 +1,136 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, stats;
+let composer, renderer, mixer, clock;
+
+const params = {
+    threshold: 0,
+    strength: 1,
+    radius: 0,
+    exposure: 1,
+};
+
+init();
+
+async function init() {
+    const container = document.getElementById('container');
+
+    clock = new THREE.Clock();
+
+    const scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(-5, 2.5, -3.5);
+    scene.add(camera);
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const pointLight = new THREE.PointLight(0xffffff, 100);
+    camera.add(pointLight);
+
+    const loader = new GLTFLoader();
+    const gltf = await loader.loadAsync('models/gltf/PrimaryIonDrive.glb');
+
+    const model = gltf.scene;
+    scene.add(model);
+
+    mixer = new THREE.AnimationMixer(model);
+    const clip = gltf.animations[0];
+    mixer.clipAction(clip.optimize()).play();
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const renderScene = new RenderPass(scene, camera);
+
+    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
+    bloomPass.threshold = params.threshold;
+    bloomPass.strength = params.strength;
+    bloomPass.radius = params.radius;
+
+    const outputPass = new OutputPass();
+
+    composer = new EffectComposer(renderer);
+    composer.addPass(renderScene);
+    composer.addPass(bloomPass);
+    composer.addPass(outputPass);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI * 0.5;
+    controls.minDistance = 3;
+    controls.maxDistance = 8;
+
+    //
+
+    const gui = new GUI();
+
+    const bloomFolder = gui.addFolder('bloom');
+
+    bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
+        bloomPass.threshold = Number(value);
+    });
+
+    bloomFolder.add(params, 'strength', 0.0, 3.0).onChange(function (value) {
+        bloomPass.strength = Number(value);
+    });
+
+    gui.add(params, 'radius', 0.0, 1.0)
+        .step(0.01)
+        .onChange(function (value) {
+            bloomPass.radius = Number(value);
+        });
+
+    const toneMappingFolder = gui.addFolder('tone mapping');
+
+    toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
+        renderer.toneMappingExposure = Math.pow(value, 4.0);
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+    composer.setSize(width, height);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    mixer.update(delta);
+
+    stats.update();
+
+    composer.render();
+}
diff --git a/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts b/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
new file mode 100644
index 000000000..d633806ee
--- /dev/null
+++ b/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
@@ -0,0 +1,195 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+const BLOOM_SCENE = 1;
+
+const bloomLayer = new THREE.Layers();
+bloomLayer.set(BLOOM_SCENE);
+
+const params = {
+    threshold: 0,
+    strength: 1,
+    radius: 0.5,
+    exposure: 1,
+};
+
+const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
+const materials = {};
+
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.toneMapping = THREE.ReinhardToneMapping;
+document.body.appendChild(renderer.domElement);
+
+const scene = new THREE.Scene();
+
+const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
+camera.position.set(0, 0, 20);
+camera.lookAt(0, 0, 0);
+
+const controls = new OrbitControls(camera, renderer.domElement);
+controls.maxPolarAngle = Math.PI * 0.5;
+controls.minDistance = 1;
+controls.maxDistance = 100;
+controls.addEventListener('change', render);
+
+const renderScene = new RenderPass(scene, camera);
+
+const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
+bloomPass.threshold = params.threshold;
+bloomPass.strength = params.strength;
+bloomPass.radius = params.radius;
+
+const bloomComposer = new EffectComposer(renderer);
+bloomComposer.renderToScreen = false;
+bloomComposer.addPass(renderScene);
+bloomComposer.addPass(bloomPass);
+
+const mixPass = new ShaderPass(
+    new THREE.ShaderMaterial({
+        uniforms: {
+            baseTexture: { value: null },
+            bloomTexture: { value: bloomComposer.renderTarget2.texture },
+        },
+        vertexShader: document.getElementById('vertexshader').textContent,
+        fragmentShader: document.getElementById('fragmentshader').textContent,
+        defines: {},
+    }),
+    'baseTexture',
+);
+mixPass.needsSwap = true;
+
+const outputPass = new OutputPass();
+
+const finalComposer = new EffectComposer(renderer);
+finalComposer.addPass(renderScene);
+finalComposer.addPass(mixPass);
+finalComposer.addPass(outputPass);
+
+const raycaster = new THREE.Raycaster();
+
+const mouse = new THREE.Vector2();
+
+window.addEventListener('pointerdown', onPointerDown);
+
+const gui = new GUI();
+
+const bloomFolder = gui.addFolder('bloom');
+
+bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
+    bloomPass.threshold = Number(value);
+    render();
+});
+
+bloomFolder.add(params, 'strength', 0.0, 3).onChange(function (value) {
+    bloomPass.strength = Number(value);
+    render();
+});
+
+bloomFolder
+    .add(params, 'radius', 0.0, 1.0)
+    .step(0.01)
+    .onChange(function (value) {
+        bloomPass.radius = Number(value);
+        render();
+    });
+
+const toneMappingFolder = gui.addFolder('tone mapping');
+
+toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
+    renderer.toneMappingExposure = Math.pow(value, 4.0);
+    render();
+});
+
+setupScene();
+
+function onPointerDown(event) {
+    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+    raycaster.setFromCamera(mouse, camera);
+    const intersects = raycaster.intersectObjects(scene.children, false);
+    if (intersects.length > 0) {
+        const object = intersects[0].object;
+        object.layers.toggle(BLOOM_SCENE);
+        render();
+    }
+}
+
+window.onresize = function () {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+
+    bloomComposer.setSize(width, height);
+    finalComposer.setSize(width, height);
+
+    render();
+};
+
+function setupScene() {
+    scene.traverse(disposeMaterial);
+    scene.children.length = 0;
+
+    const geometry = new THREE.IcosahedronGeometry(1, 15);
+
+    for (let i = 0; i < 50; i++) {
+        const color = new THREE.Color();
+        color.setHSL(Math.random(), 0.7, Math.random() * 0.2 + 0.05);
+
+        const material = new THREE.MeshBasicMaterial({ color: color });
+        const sphere = new THREE.Mesh(geometry, material);
+        sphere.position.x = Math.random() * 10 - 5;
+        sphere.position.y = Math.random() * 10 - 5;
+        sphere.position.z = Math.random() * 10 - 5;
+        sphere.position.normalize().multiplyScalar(Math.random() * 4.0 + 2.0);
+        sphere.scale.setScalar(Math.random() * Math.random() + 0.5);
+        scene.add(sphere);
+
+        if (Math.random() < 0.25) sphere.layers.enable(BLOOM_SCENE);
+    }
+
+    render();
+}
+
+function disposeMaterial(obj) {
+    if (obj.material) {
+        obj.material.dispose();
+    }
+}
+
+function render() {
+    scene.traverse(darkenNonBloomed);
+    bloomComposer.render();
+    scene.traverse(restoreMaterial);
+
+    // render the entire scene, then render bloom scene on top
+    finalComposer.render();
+}
+
+function darkenNonBloomed(obj) {
+    if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
+        materials[obj.uuid] = obj.material;
+        obj.material = darkMaterial;
+    }
+}
+
+function restoreMaterial(obj) {
+    if (materials[obj.uuid]) {
+        obj.material = materials[obj.uuid];
+        delete materials[obj.uuid];
+    }
+}
diff --git a/examples-testing/examples/webgl_raycaster_sprite.ts b/examples-testing/examples/webgl_raycaster_sprite.ts
new file mode 100644
index 000000000..f35d5de17
--- /dev/null
+++ b/examples-testing/examples/webgl_raycaster_sprite.ts
@@ -0,0 +1,103 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let renderer, scene, camera;
+let group;
+
+let selectedObject = null;
+const raycaster = new THREE.Raycaster();
+const pointer = new THREE.Vector2();
+
+init();
+
+function init() {
+    // init renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // init scene
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    // init camera
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(15, 15, 15);
+    camera.lookAt(scene.position);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 15;
+    controls.maxDistance = 250;
+
+    // add sprites
+
+    const sprite1 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f' }));
+    sprite1.position.set(6, 5, 5);
+    sprite1.scale.set(2, 5, 1);
+    group.add(sprite1);
+
+    const sprite2 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f', sizeAttenuation: false }));
+    sprite2.material.rotation = (Math.PI / 3) * 4;
+    sprite2.position.set(8, -2, 2);
+    sprite2.center.set(0.5, 0);
+    sprite2.scale.set(0.1, 0.5, 0.1);
+    group.add(sprite2);
+
+    const group2 = new THREE.Object3D();
+    group2.scale.set(1, 2, 1);
+    group2.position.set(-5, 0, 0);
+    group2.rotation.set(Math.PI / 2, 0, 0);
+    group.add(group2);
+
+    const sprite3 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f' }));
+    sprite3.position.set(0, 2, 5);
+    sprite3.scale.set(10, 2, 3);
+    sprite3.center.set(-0.1, 0);
+    sprite3.material.rotation = Math.PI / 3;
+    group2.add(sprite3);
+
+    window.addEventListener('resize', onWindowResize);
+    document.addEventListener('pointermove', onPointerMove);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerMove(event) {
+    if (selectedObject) {
+        selectedObject.material.color.set('#69f');
+        selectedObject = null;
+    }
+
+    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
+    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+    raycaster.setFromCamera(pointer, camera);
+
+    const intersects = raycaster.intersectObject(group, true);
+
+    if (intersects.length > 0) {
+        const res = intersects.filter(function (res) {
+            return res && res.object;
+        })[0];
+
+        if (res && res.object) {
+            selectedObject = res.object;
+            selectedObject.material.color.set('#f00');
+        }
+    }
+}
diff --git a/examples-testing/examples/webgl_raycaster_texture.ts b/examples-testing/examples/webgl_raycaster_texture.ts
new file mode 100644
index 000000000..72c7054dc
--- /dev/null
+++ b/examples-testing/examples/webgl_raycaster_texture.ts
@@ -0,0 +1,286 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const WRAPPING = {
+    RepeatWrapping: THREE.RepeatWrapping,
+    ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
+    MirroredRepeatWrapping: THREE.MirroredRepeatWrapping,
+};
+
+const params = {
+    wrapS: THREE.RepeatWrapping,
+    wrapT: THREE.RepeatWrapping,
+    offsetX: 0,
+    offsetY: 0,
+    repeatX: 1,
+    repeatY: 1,
+    rotation: 0,
+};
+
+function CanvasTexture(parentTexture) {
+    this._canvas = document.createElement('canvas');
+    this._canvas.width = this._canvas.height = 1024;
+    this._context2D = this._canvas.getContext('2d');
+
+    if (parentTexture) {
+        this._parentTexture.push(parentTexture);
+        parentTexture.image = this._canvas;
+    }
+
+    const that = this;
+    this._background = document.createElement('img');
+    this._background.addEventListener('load', function () {
+        that._canvas.width = that._background.naturalWidth;
+        that._canvas.height = that._background.naturalHeight;
+
+        that._crossRadius = Math.ceil(Math.min(that._canvas.width, that._canvas.height / 30));
+        that._crossMax = Math.ceil(0.70710678 * that._crossRadius);
+        that._crossMin = Math.ceil(that._crossMax / 10);
+        that._crossThickness = Math.ceil(that._crossMax / 10);
+
+        that._draw();
+    });
+    this._background.crossOrigin = '';
+    this._background.src = 'textures/uv_grid_opengl.jpg';
+
+    this._draw();
+}
+
+CanvasTexture.prototype = {
+    constructor: CanvasTexture,
+
+    _canvas: null,
+    _context2D: null,
+    _xCross: 0,
+    _yCross: 0,
+
+    _crossRadius: 57,
+    _crossMax: 40,
+    _crossMin: 4,
+    _crossThickness: 4,
+
+    _parentTexture: [],
+
+    addParent: function (parentTexture) {
+        if (this._parentTexture.indexOf(parentTexture) === -1) {
+            this._parentTexture.push(parentTexture);
+            parentTexture.image = this._canvas;
+        }
+    },
+
+    setCrossPosition: function (x, y) {
+        this._xCross = x * this._canvas.width;
+        this._yCross = y * this._canvas.height;
+
+        this._draw();
+    },
+
+    _draw: function () {
+        if (!this._context2D) return;
+
+        this._context2D.clearRect(0, 0, this._canvas.width, this._canvas.height);
+
+        // Background.
+        this._context2D.drawImage(this._background, 0, 0);
+
+        // Yellow cross.
+        this._context2D.lineWidth = this._crossThickness * 3;
+        this._context2D.strokeStyle = '#FFFF00';
+
+        this._context2D.beginPath();
+        this._context2D.moveTo(this._xCross - this._crossMax - 2, this._yCross - this._crossMax - 2);
+        this._context2D.lineTo(this._xCross - this._crossMin, this._yCross - this._crossMin);
+
+        this._context2D.moveTo(this._xCross + this._crossMin, this._yCross + this._crossMin);
+        this._context2D.lineTo(this._xCross + this._crossMax + 2, this._yCross + this._crossMax + 2);
+
+        this._context2D.moveTo(this._xCross - this._crossMax - 2, this._yCross + this._crossMax + 2);
+        this._context2D.lineTo(this._xCross - this._crossMin, this._yCross + this._crossMin);
+
+        this._context2D.moveTo(this._xCross + this._crossMin, this._yCross - this._crossMin);
+        this._context2D.lineTo(this._xCross + this._crossMax + 2, this._yCross - this._crossMax - 2);
+
+        this._context2D.stroke();
+
+        for (let i = 0; i < this._parentTexture.length; i++) {
+            this._parentTexture[i].needsUpdate = true;
+        }
+    },
+};
+
+const width = window.innerWidth;
+const height = window.innerHeight;
+
+let canvas;
+let planeTexture, cubeTexture, circleTexture;
+
+let container;
+
+let camera, scene, renderer;
+
+const raycaster = new THREE.Raycaster();
+const mouse = new THREE.Vector2();
+const onClickPosition = new THREE.Vector2();
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xeeeeee);
+
+    camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000);
+    camera.position.x = -30;
+    camera.position.y = 40;
+    camera.position.z = 50;
+    camera.lookAt(scene.position);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(width, height);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // A cube, in the middle.
+    cubeTexture = new THREE.Texture(undefined, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping);
+    cubeTexture.colorSpace = THREE.SRGBColorSpace;
+    canvas = new CanvasTexture(cubeTexture);
+    const cubeMaterial = new THREE.MeshBasicMaterial({ map: cubeTexture });
+    const cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
+    let uvs = cubeGeometry.attributes.uv.array;
+    // Set a specific texture mapping.
+    for (let i = 0; i < uvs.length; i++) {
+        uvs[i] *= 2;
+    }
+
+    const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
+    cube.position.x = 4;
+    cube.position.y = -5;
+    cube.position.z = 0;
+    scene.add(cube);
+
+    // A plane on the left
+
+    planeTexture = new THREE.Texture(
+        undefined,
+        THREE.UVMapping,
+        THREE.MirroredRepeatWrapping,
+        THREE.MirroredRepeatWrapping,
+    );
+    planeTexture.colorSpace = THREE.SRGBColorSpace;
+    canvas.addParent(planeTexture);
+    const planeMaterial = new THREE.MeshBasicMaterial({ map: planeTexture });
+    const planeGeometry = new THREE.PlaneGeometry(25, 25, 1, 1);
+    uvs = planeGeometry.attributes.uv.array;
+
+    // Set a specific texture mapping.
+
+    for (let i = 0; i < uvs.length; i++) {
+        uvs[i] *= 2;
+    }
+
+    const plane = new THREE.Mesh(planeGeometry, planeMaterial);
+    plane.position.x = -16;
+    plane.position.y = -5;
+    plane.position.z = 0;
+    scene.add(plane);
+
+    // A circle on the right.
+
+    circleTexture = new THREE.Texture(undefined, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping);
+    circleTexture.colorSpace = THREE.SRGBColorSpace;
+    canvas.addParent(circleTexture);
+    const circleMaterial = new THREE.MeshBasicMaterial({ map: circleTexture });
+    const circleGeometry = new THREE.CircleGeometry(25, 40, 0, Math.PI * 2);
+    uvs = circleGeometry.attributes.uv.array;
+
+    // Set a specific texture mapping.
+
+    for (let i = 0; i < uvs.length; i++) {
+        uvs[i] = (uvs[i] - 0.25) * 2;
+    }
+
+    const circle = new THREE.Mesh(circleGeometry, circleMaterial);
+    circle.position.x = 24;
+    circle.position.y = -5;
+    circle.position.z = 0;
+    scene.add(circle);
+
+    window.addEventListener('resize', onWindowResize);
+    container.addEventListener('mousemove', onMouseMove);
+
+    //
+
+    const gui = new GUI();
+    gui.title('Circle Texture Settings');
+
+    gui.add(params, 'wrapS', WRAPPING).onChange(setwrapS);
+    gui.add(params, 'wrapT', WRAPPING).onChange(setwrapT);
+    gui.add(params, 'offsetX', 0, 5);
+    gui.add(params, 'offsetY', 0, 5);
+    gui.add(params, 'repeatX', 0, 5);
+    gui.add(params, 'repeatY', 0, 5);
+    gui.add(params, 'rotation', 0, 2 * Math.PI);
+    gui.open();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onMouseMove(evt) {
+    evt.preventDefault();
+
+    const array = getMousePosition(container, evt.clientX, evt.clientY);
+    onClickPosition.fromArray(array);
+
+    const intersects = getIntersects(onClickPosition, scene.children);
+
+    if (intersects.length > 0 && intersects[0].uv) {
+        const uv = intersects[0].uv;
+        intersects[0].object.material.map.transformUv(uv);
+        canvas.setCrossPosition(uv.x, uv.y);
+    }
+}
+
+function getMousePosition(dom, x, y) {
+    const rect = dom.getBoundingClientRect();
+    return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
+}
+
+function getIntersects(point, objects) {
+    mouse.set(point.x * 2 - 1, -(point.y * 2) + 1);
+
+    raycaster.setFromCamera(mouse, camera);
+
+    return raycaster.intersectObjects(objects, false);
+}
+
+function animate() {
+    // update texture parameters
+
+    circleTexture.offset.x = params.offsetX;
+    circleTexture.offset.y = params.offsetY;
+    circleTexture.repeat.x = params.repeatX;
+    circleTexture.repeat.y = params.repeatY;
+    circleTexture.rotation = params.rotation;
+
+    //
+
+    renderer.render(scene, camera);
+}
+
+function setwrapS(value) {
+    circleTexture.wrapS = value;
+    circleTexture.needsUpdate = true;
+}
+
+function setwrapT(value) {
+    circleTexture.wrapT = value;
+    circleTexture.needsUpdate = true;
+}
diff --git a/examples-testing/examples/webgl_raymarching_reflect.ts b/examples-testing/examples/webgl_raymarching_reflect.ts
new file mode 100644
index 000000000..e5448ebbd
--- /dev/null
+++ b/examples-testing/examples/webgl_raymarching_reflect.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let dolly, camera, scene, renderer;
+let geometry, material, mesh;
+let stats, clock;
+
+const canvas = document.querySelector('#canvas');
+
+const config = {
+    saveImage: function () {
+        renderer.render(scene, camera);
+        window.open(canvas.toDataURL());
+    },
+    resolution: '512',
+};
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer({ canvas: canvas });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(parseInt(config.resolution), parseInt(config.resolution));
+    renderer.setAnimationLoop(animate);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // THREE.Scene
+    scene = new THREE.Scene();
+
+    dolly = new THREE.Group();
+    scene.add(dolly);
+
+    clock = new THREE.Clock();
+
+    camera = new THREE.PerspectiveCamera(60, canvas.width / canvas.height, 1, 2000);
+    camera.position.z = 4;
+    dolly.add(camera);
+
+    geometry = new THREE.PlaneGeometry(2.0, 2.0);
+    material = new THREE.RawShaderMaterial({
+        uniforms: {
+            resolution: { value: new THREE.Vector2(canvas.width, canvas.height) },
+            cameraWorldMatrix: { value: camera.matrixWorld },
+            cameraProjectionMatrixInverse: { value: camera.projectionMatrixInverse.clone() },
+        },
+        vertexShader: document.getElementById('vertex_shader').textContent,
+        fragmentShader: document.getElementById('fragment_shader').textContent,
+    });
+    mesh = new THREE.Mesh(geometry, material);
+    mesh.frustumCulled = false;
+    scene.add(mesh);
+
+    // Controls
+    const controls = new OrbitControls(camera, canvas);
+    controls.enableZoom = false;
+
+    // GUI
+    const gui = new GUI();
+    gui.add(config, 'saveImage').name('Save Image');
+    gui.add(config, 'resolution', ['256', '512', '800', 'full']).name('Resolution').onChange(onWindowResize);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+}
+
+function onWindowResize() {
+    if (config.resolution === 'full') {
+        renderer.setSize(window.innerWidth, window.innerHeight);
+    } else {
+        renderer.setSize(parseInt(config.resolution), parseInt(config.resolution));
+    }
+
+    camera.aspect = canvas.width / canvas.height;
+    camera.updateProjectionMatrix();
+
+    material.uniforms.resolution.value.set(canvas.width, canvas.height);
+    material.uniforms.cameraProjectionMatrixInverse.value.copy(camera.projectionMatrixInverse);
+}
+
+function animate() {
+    stats.begin();
+
+    const elapsedTime = clock.getElapsedTime();
+
+    dolly.position.z = -elapsedTime;
+
+    renderer.render(scene, camera);
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgl_read_float_buffer.ts b/examples-testing/examples/webgl_read_float_buffer.ts
new file mode 100644
index 000000000..68452a12a
--- /dev/null
+++ b/examples-testing/examples/webgl_read_float_buffer.ts
@@ -0,0 +1,153 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let cameraRTT, sceneRTT, sceneScreen, renderer, zmesh1, zmesh2;
+
+let mouseX = 0,
+    mouseY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+let rtTexture, material, quad;
+
+let delta = 0.01;
+let valueNode;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    cameraRTT = new THREE.OrthographicCamera(
+        window.innerWidth / -2,
+        window.innerWidth / 2,
+        window.innerHeight / 2,
+        window.innerHeight / -2,
+        -10000,
+        10000,
+    );
+    cameraRTT.position.z = 100;
+
+    //
+
+    sceneRTT = new THREE.Scene();
+    sceneScreen = new THREE.Scene();
+
+    let light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 0, 1).normalize();
+    sceneRTT.add(light);
+
+    light = new THREE.DirectionalLight(0xffd5d5, 4.5);
+    light.position.set(0, 0, -1).normalize();
+    sceneRTT.add(light);
+
+    rtTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
+        minFilter: THREE.LinearFilter,
+        magFilter: THREE.NearestFilter,
+        format: THREE.RGBAFormat,
+        type: THREE.FloatType,
+    });
+
+    material = new THREE.ShaderMaterial({
+        uniforms: { time: { value: 0.0 } },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragment_shader_pass_1').textContent,
+    });
+
+    const materialScreen = new THREE.ShaderMaterial({
+        uniforms: { tDiffuse: { value: rtTexture.texture } },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragment_shader_screen').textContent,
+
+        depthWrite: false,
+    });
+
+    const plane = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
+
+    quad = new THREE.Mesh(plane, material);
+    quad.position.z = -100;
+    sceneRTT.add(quad);
+
+    const geometry = new THREE.TorusGeometry(100, 25, 15, 30);
+
+    const mat1 = new THREE.MeshPhongMaterial({ color: 0x9c9c9c, specular: 0xffaa00, shininess: 5 });
+    const mat2 = new THREE.MeshPhongMaterial({ color: 0x9c0000, specular: 0xff2200, shininess: 5 });
+
+    zmesh1 = new THREE.Mesh(geometry, mat1);
+    zmesh1.position.set(0, 0, 100);
+    zmesh1.scale.set(1.5, 1.5, 1.5);
+    sceneRTT.add(zmesh1);
+
+    zmesh2 = new THREE.Mesh(geometry, mat2);
+    zmesh2.position.set(0, 150, 100);
+    zmesh2.scale.set(0.75, 0.75, 0.75);
+    sceneRTT.add(zmesh2);
+
+    quad = new THREE.Mesh(plane, materialScreen);
+    quad.position.z = -100;
+    sceneScreen.add(quad);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    valueNode = document.getElementById('values');
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.0015;
+
+    if (zmesh1 && zmesh2) {
+        zmesh1.rotation.y = -time;
+        zmesh2.rotation.y = -time + Math.PI / 2;
+    }
+
+    if (material.uniforms['time'].value > 1 || material.uniforms['time'].value < 0) {
+        delta *= -1;
+    }
+
+    material.uniforms['time'].value += delta;
+
+    renderer.clear();
+
+    // Render first scene into texture
+
+    renderer.setRenderTarget(rtTexture);
+    renderer.clear();
+    renderer.render(sceneRTT, cameraRTT);
+
+    // Render full screen quad with generated texture
+
+    renderer.setRenderTarget(null);
+    renderer.render(sceneScreen, cameraRTT);
+
+    const read = new Float32Array(4);
+    renderer.readRenderTargetPixels(rtTexture, windowHalfX + mouseX, windowHalfY - mouseY, 1, 1, read);
+
+    valueNode.innerHTML = 'r:' + read[0] + '<br/>g:' + read[1] + '<br/>b:' + read[2];
+}
diff --git a/examples-testing/examples/webgl_refraction.ts b/examples-testing/examples/webgl_refraction.ts
new file mode 100644
index 000000000..572575afa
--- /dev/null
+++ b/examples-testing/examples/webgl_refraction.ts
@@ -0,0 +1,135 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Refractor } from 'three/addons/objects/Refractor.js';
+import { WaterRefractionShader } from 'three/addons/shaders/WaterRefractionShader.js';
+
+let camera, scene, renderer, clock;
+
+let refractor, smallSphere;
+
+init();
+
+async function init() {
+    const container = document.getElementById('container');
+
+    clock = new THREE.Clock();
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.set(0, 75, 160);
+
+    // refractor
+
+    const refractorGeometry = new THREE.PlaneGeometry(90, 90);
+
+    refractor = new Refractor(refractorGeometry, {
+        color: 0xcbcbcb,
+        textureWidth: 1024,
+        textureHeight: 1024,
+        shader: WaterRefractionShader,
+    });
+
+    refractor.position.set(0, 50, 0);
+
+    scene.add(refractor);
+
+    // load dudv map for distortion effect
+
+    const loader = new THREE.TextureLoader();
+    const dudvMap = await loader.loadAsync('textures/waterdudv.jpg');
+
+    dudvMap.wrapS = dudvMap.wrapT = THREE.RepeatWrapping;
+    refractor.material.uniforms.tDudv.value = dudvMap;
+
+    //
+
+    const geometry = new THREE.IcosahedronGeometry(5, 0);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x333333, flatShading: true });
+    smallSphere = new THREE.Mesh(geometry, material);
+    scene.add(smallSphere);
+
+    // walls
+    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
+
+    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeTop.position.y = 100;
+    planeTop.rotateX(Math.PI / 2);
+    scene.add(planeTop);
+
+    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeBottom.rotateX(-Math.PI / 2);
+    scene.add(planeBottom);
+
+    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
+    planeBack.position.z = -50;
+    planeBack.position.y = 50;
+    scene.add(planeBack);
+
+    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
+    planeRight.position.x = 50;
+    planeRight.position.y = 50;
+    planeRight.rotateY(-Math.PI / 2);
+    scene.add(planeRight);
+
+    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
+    planeLeft.position.x = -50;
+    planeLeft.position.y = 50;
+    planeLeft.rotateY(Math.PI / 2);
+    scene.add(planeLeft);
+
+    // lights
+    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
+    mainLight.position.y = 60;
+    scene.add(mainLight);
+
+    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
+    greenLight.position.set(550, 50, 0);
+    scene.add(greenLight);
+
+    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
+    redLight.position.set(-550, 50, 0);
+    scene.add(redLight);
+
+    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
+    blueLight.position.set(0, 50, 550);
+    scene.add(blueLight);
+
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 40, 0);
+    controls.maxDistance = 400;
+    controls.minDistance = 10;
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = clock.getElapsedTime();
+
+    refractor.material.uniforms.time.value = time;
+
+    smallSphere.position.set(Math.cos(time) * 30, Math.abs(Math.cos(time * 2)) * 20 + 5, Math.sin(time) * 30);
+    smallSphere.rotation.y = Math.PI / 2 - time;
+    smallSphere.rotation.z = time * 8;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_rtt.ts b/examples-testing/examples/webgl_rtt.ts
new file mode 100644
index 000000000..9f16fdab8
--- /dev/null
+++ b/examples-testing/examples/webgl_rtt.ts
@@ -0,0 +1,171 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let cameraRTT, camera, sceneRTT, sceneScreen, scene, renderer, zmesh1, zmesh2;
+
+let mouseX = 0,
+    mouseY = 0;
+
+const windowHalfX = window.innerWidth / 2;
+const windowHalfY = window.innerHeight / 2;
+
+let rtTexture, material, quad;
+
+let delta = 0.01;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 100;
+
+    cameraRTT = new THREE.OrthographicCamera(
+        window.innerWidth / -2,
+        window.innerWidth / 2,
+        window.innerHeight / 2,
+        window.innerHeight / -2,
+        -10000,
+        10000,
+    );
+    cameraRTT.position.z = 100;
+
+    //
+
+    scene = new THREE.Scene();
+    sceneRTT = new THREE.Scene();
+    sceneScreen = new THREE.Scene();
+
+    let light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 0, 1).normalize();
+    sceneRTT.add(light);
+
+    light = new THREE.DirectionalLight(0xffd5d5, 4.5);
+    light.position.set(0, 0, -1).normalize();
+    sceneRTT.add(light);
+
+    rtTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
+
+    material = new THREE.ShaderMaterial({
+        uniforms: { time: { value: 0.0 } },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragment_shader_pass_1').textContent,
+    });
+
+    const materialScreen = new THREE.ShaderMaterial({
+        uniforms: { tDiffuse: { value: rtTexture.texture } },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragment_shader_screen').textContent,
+
+        depthWrite: false,
+    });
+
+    const plane = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
+
+    quad = new THREE.Mesh(plane, material);
+    quad.position.z = -100;
+    sceneRTT.add(quad);
+
+    const torusGeometry = new THREE.TorusGeometry(100, 25, 15, 30);
+
+    const mat1 = new THREE.MeshPhongMaterial({ color: 0x9c9c9c, specular: 0xffaa00, shininess: 5 });
+    const mat2 = new THREE.MeshPhongMaterial({ color: 0x9c0000, specular: 0xff2200, shininess: 5 });
+
+    zmesh1 = new THREE.Mesh(torusGeometry, mat1);
+    zmesh1.position.set(0, 0, 100);
+    zmesh1.scale.set(1.5, 1.5, 1.5);
+    sceneRTT.add(zmesh1);
+
+    zmesh2 = new THREE.Mesh(torusGeometry, mat2);
+    zmesh2.position.set(0, 150, 100);
+    zmesh2.scale.set(0.75, 0.75, 0.75);
+    sceneRTT.add(zmesh2);
+
+    quad = new THREE.Mesh(plane, materialScreen);
+    quad.position.z = -100;
+    sceneScreen.add(quad);
+
+    const n = 5,
+        geometry = new THREE.SphereGeometry(10, 64, 32),
+        material2 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: rtTexture.texture });
+
+    for (let j = 0; j < n; j++) {
+        for (let i = 0; i < n; i++) {
+            const mesh = new THREE.Mesh(geometry, material2);
+
+            mesh.position.x = (i - (n - 1) / 2) * 20;
+            mesh.position.y = (j - (n - 1) / 2) * 20;
+            mesh.position.z = 0;
+
+            mesh.rotation.y = -Math.PI / 2;
+
+            scene.add(mesh);
+        }
+    }
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    container.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+//
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = Date.now() * 0.0015;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    if (zmesh1 && zmesh2) {
+        zmesh1.rotation.y = -time;
+        zmesh2.rotation.y = -time + Math.PI / 2;
+    }
+
+    if (material.uniforms['time'].value > 1 || material.uniforms['time'].value < 0) {
+        delta *= -1;
+    }
+
+    material.uniforms['time'].value += delta;
+
+    // Render first scene into texture
+
+    renderer.setRenderTarget(rtTexture);
+    renderer.clear();
+    renderer.render(sceneRTT, cameraRTT);
+
+    // Render full screen quad with generated texture
+
+    renderer.setRenderTarget(null);
+    renderer.clear();
+    renderer.render(sceneScreen, cameraRTT);
+
+    // Render second scene to screen
+    // (using first scene as regular texture)
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shader.ts b/examples-testing/examples/webgl_shader.ts
new file mode 100644
index 000000000..47a6c7ece
--- /dev/null
+++ b/examples-testing/examples/webgl_shader.ts
@@ -0,0 +1,50 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+
+let uniforms;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.PlaneGeometry(2, 2);
+
+    uniforms = {
+        time: { value: 1.0 },
+    };
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+    });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    uniforms['time'].value = performance.now() / 1000;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shader_lava.ts b/examples-testing/examples/webgl_shader_lava.ts
new file mode 100644
index 000000000..973a580eb
--- /dev/null
+++ b/examples-testing/examples/webgl_shader_lava.ts
@@ -0,0 +1,101 @@
+import * as THREE from 'three';
+
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+let camera, renderer, composer, clock;
+
+let uniforms, mesh;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 3000);
+    camera.position.z = 4;
+
+    const scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const cloudTexture = textureLoader.load('textures/lava/cloud.png');
+    const lavaTexture = textureLoader.load('textures/lava/lavatile.jpg');
+
+    lavaTexture.colorSpace = THREE.SRGBColorSpace;
+
+    cloudTexture.wrapS = cloudTexture.wrapT = THREE.RepeatWrapping;
+    lavaTexture.wrapS = lavaTexture.wrapT = THREE.RepeatWrapping;
+
+    uniforms = {
+        fogDensity: { value: 0.45 },
+        fogColor: { value: new THREE.Vector3(0, 0, 0) },
+        time: { value: 1.0 },
+        uvScale: { value: new THREE.Vector2(3.0, 1.0) },
+        texture1: { value: cloudTexture },
+        texture2: { value: lavaTexture },
+    };
+
+    const size = 0.65;
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: uniforms,
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+    });
+
+    mesh = new THREE.Mesh(new THREE.TorusGeometry(size, 0.3, 30, 30), material);
+    mesh.rotation.x = 0.3;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const renderModel = new RenderPass(scene, camera);
+    const effectBloom = new BloomPass(1.25);
+    const outputPass = new OutputPass();
+
+    composer = new EffectComposer(renderer);
+
+    composer.addPass(renderModel);
+    composer.addPass(effectBloom);
+    composer.addPass(outputPass);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    composer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const delta = 5 * clock.getDelta();
+
+    uniforms['time'].value += 0.2 * delta;
+
+    mesh.rotation.y += 0.0125 * delta;
+    mesh.rotation.x += 0.05 * delta;
+
+    renderer.clear();
+    composer.render(0.01);
+}
diff --git a/examples-testing/examples/webgl_shaders_ocean.ts b/examples-testing/examples/webgl_shaders_ocean.ts
new file mode 100644
index 000000000..8b0f9a738
--- /dev/null
+++ b/examples-testing/examples/webgl_shaders_ocean.ts
@@ -0,0 +1,169 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Water } from 'three/addons/objects/Water.js';
+import { Sky } from 'three/addons/objects/Sky.js';
+
+let container, stats;
+let camera, scene, renderer;
+let controls, water, sun, mesh;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 0.5;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
+    camera.position.set(30, 30, 100);
+
+    //
+
+    sun = new THREE.Vector3();
+
+    // Water
+
+    const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
+
+    water = new Water(waterGeometry, {
+        textureWidth: 512,
+        textureHeight: 512,
+        waterNormals: new THREE.TextureLoader().load('textures/waternormals.jpg', function (texture) {
+            texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+        }),
+        sunDirection: new THREE.Vector3(),
+        sunColor: 0xffffff,
+        waterColor: 0x001e0f,
+        distortionScale: 3.7,
+        fog: scene.fog !== undefined,
+    });
+
+    water.rotation.x = -Math.PI / 2;
+
+    scene.add(water);
+
+    // Skybox
+
+    const sky = new Sky();
+    sky.scale.setScalar(10000);
+    scene.add(sky);
+
+    const skyUniforms = sky.material.uniforms;
+
+    skyUniforms['turbidity'].value = 10;
+    skyUniforms['rayleigh'].value = 2;
+    skyUniforms['mieCoefficient'].value = 0.005;
+    skyUniforms['mieDirectionalG'].value = 0.8;
+
+    const parameters = {
+        elevation: 2,
+        azimuth: 180,
+    };
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    const sceneEnv = new THREE.Scene();
+
+    let renderTarget;
+
+    function updateSun() {
+        const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
+        const theta = THREE.MathUtils.degToRad(parameters.azimuth);
+
+        sun.setFromSphericalCoords(1, phi, theta);
+
+        sky.material.uniforms['sunPosition'].value.copy(sun);
+        water.material.uniforms['sunDirection'].value.copy(sun).normalize();
+
+        if (renderTarget !== undefined) renderTarget.dispose();
+
+        sceneEnv.add(sky);
+        renderTarget = pmremGenerator.fromScene(sceneEnv);
+        scene.add(sky);
+
+        scene.environment = renderTarget.texture;
+    }
+
+    updateSun();
+
+    //
+
+    const geometry = new THREE.BoxGeometry(30, 30, 30);
+    const material = new THREE.MeshStandardMaterial({ roughness: 0 });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI * 0.495;
+    controls.target.set(0, 10, 0);
+    controls.minDistance = 40.0;
+    controls.maxDistance = 200.0;
+    controls.update();
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // GUI
+
+    const gui = new GUI();
+
+    const folderSky = gui.addFolder('Sky');
+    folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun);
+    folderSky.add(parameters, 'azimuth', -180, 180, 0.1).onChange(updateSun);
+    folderSky.open();
+
+    const waterUniforms = water.material.uniforms;
+
+    const folderWater = gui.addFolder('Water');
+    folderWater.add(waterUniforms.distortionScale, 'value', 0, 8, 0.1).name('distortionScale');
+    folderWater.add(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size');
+    folderWater.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = performance.now() * 0.001;
+
+    mesh.position.y = Math.sin(time) * 20 + 5;
+    mesh.rotation.x = time * 0.5;
+    mesh.rotation.z = time * 0.51;
+
+    water.material.uniforms['time'].value += 1.0 / 60.0;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shaders_sky.ts b/examples-testing/examples/webgl_shaders_sky.ts
new file mode 100644
index 000000000..18020f78f
--- /dev/null
+++ b/examples-testing/examples/webgl_shaders_sky.ts
@@ -0,0 +1,103 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Sky } from 'three/addons/objects/Sky.js';
+
+let camera, scene, renderer;
+
+let sky, sun;
+
+init();
+render();
+
+function initSky() {
+    // Add Sky
+    sky = new Sky();
+    sky.scale.setScalar(450000);
+    scene.add(sky);
+
+    sun = new THREE.Vector3();
+
+    /// GUI
+
+    const effectController = {
+        turbidity: 10,
+        rayleigh: 3,
+        mieCoefficient: 0.005,
+        mieDirectionalG: 0.7,
+        elevation: 2,
+        azimuth: 180,
+        exposure: renderer.toneMappingExposure,
+    };
+
+    function guiChanged() {
+        const uniforms = sky.material.uniforms;
+        uniforms['turbidity'].value = effectController.turbidity;
+        uniforms['rayleigh'].value = effectController.rayleigh;
+        uniforms['mieCoefficient'].value = effectController.mieCoefficient;
+        uniforms['mieDirectionalG'].value = effectController.mieDirectionalG;
+
+        const phi = THREE.MathUtils.degToRad(90 - effectController.elevation);
+        const theta = THREE.MathUtils.degToRad(effectController.azimuth);
+
+        sun.setFromSphericalCoords(1, phi, theta);
+
+        uniforms['sunPosition'].value.copy(sun);
+
+        renderer.toneMappingExposure = effectController.exposure;
+        renderer.render(scene, camera);
+    }
+
+    const gui = new GUI();
+
+    gui.add(effectController, 'turbidity', 0.0, 20.0, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'rayleigh', 0.0, 4, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'mieCoefficient', 0.0, 0.1, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'mieDirectionalG', 0.0, 1, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'elevation', 0, 90, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'azimuth', -180, 180, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'exposure', 0, 1, 0.0001).onChange(guiChanged);
+
+    guiChanged();
+}
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 2000000);
+    camera.position.set(0, 100, 2000);
+
+    scene = new THREE.Scene();
+
+    const helper = new THREE.GridHelper(10000, 2, 0xffffff, 0xffffff);
+    scene.add(helper);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 0.5;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    //controls.maxPolarAngle = Math.PI / 2;
+    controls.enableZoom = false;
+    controls.enablePan = false;
+
+    initSky();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shadow_contact.ts b/examples-testing/examples/webgl_shadow_contact.ts
new file mode 100644
index 000000000..9eda35b83
--- /dev/null
+++ b/examples-testing/examples/webgl_shadow_contact.ts
@@ -0,0 +1,272 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';
+import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';
+
+let camera, scene, renderer, stats, gui;
+
+const meshes = [];
+
+const PLANE_WIDTH = 2.5;
+const PLANE_HEIGHT = 2.5;
+const CAMERA_HEIGHT = 0.3;
+
+const state = {
+    shadow: {
+        blur: 3.5,
+        darkness: 1,
+        opacity: 1,
+    },
+    plane: {
+        color: '#ffffff',
+        opacity: 1,
+    },
+    showWireframe: false,
+};
+
+let shadowGroup,
+    renderTarget,
+    renderTargetBlur,
+    shadowCamera,
+    cameraHelper,
+    depthMaterial,
+    horizontalBlurMaterial,
+    verticalBlurMaterial;
+
+let plane, blurPlane, fillPlane;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0.5, 1, 2);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // add the example meshes
+
+    const geometries = [
+        new THREE.BoxGeometry(0.4, 0.4, 0.4),
+        new THREE.IcosahedronGeometry(0.3),
+        new THREE.TorusKnotGeometry(0.4, 0.05, 256, 24, 1, 3),
+    ];
+
+    const material = new THREE.MeshNormalMaterial();
+
+    for (let i = 0, l = geometries.length; i < l; i++) {
+        const angle = (i / l) * Math.PI * 2;
+
+        const geometry = geometries[i];
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.y = 0.1;
+        mesh.position.x = Math.cos(angle) / 2.0;
+        mesh.position.z = Math.sin(angle) / 2.0;
+        scene.add(mesh);
+        meshes.push(mesh);
+    }
+
+    // the container, if you need to move the plane just move this
+    shadowGroup = new THREE.Group();
+    shadowGroup.position.y = -0.3;
+    scene.add(shadowGroup);
+
+    // the render target that will show the shadows in the plane texture
+    renderTarget = new THREE.WebGLRenderTarget(512, 512);
+    renderTarget.texture.generateMipmaps = false;
+
+    // the render target that we will use to blur the first render target
+    renderTargetBlur = new THREE.WebGLRenderTarget(512, 512);
+    renderTargetBlur.texture.generateMipmaps = false;
+
+    // make a plane and make it face up
+    const planeGeometry = new THREE.PlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT).rotateX(Math.PI / 2);
+    const planeMaterial = new THREE.MeshBasicMaterial({
+        map: renderTarget.texture,
+        opacity: state.shadow.opacity,
+        transparent: true,
+        depthWrite: false,
+    });
+    plane = new THREE.Mesh(planeGeometry, planeMaterial);
+    // make sure it's rendered after the fillPlane
+    plane.renderOrder = 1;
+    shadowGroup.add(plane);
+
+    // the y from the texture is flipped!
+    plane.scale.y = -1;
+
+    // the plane onto which to blur the texture
+    blurPlane = new THREE.Mesh(planeGeometry);
+    blurPlane.visible = false;
+    shadowGroup.add(blurPlane);
+
+    // the plane with the color of the ground
+    const fillPlaneMaterial = new THREE.MeshBasicMaterial({
+        color: state.plane.color,
+        opacity: state.plane.opacity,
+        transparent: true,
+        depthWrite: false,
+    });
+    fillPlane = new THREE.Mesh(planeGeometry, fillPlaneMaterial);
+    fillPlane.rotateX(Math.PI);
+    shadowGroup.add(fillPlane);
+
+    // the camera to render the depth material from
+    shadowCamera = new THREE.OrthographicCamera(
+        -PLANE_WIDTH / 2,
+        PLANE_WIDTH / 2,
+        PLANE_HEIGHT / 2,
+        -PLANE_HEIGHT / 2,
+        0,
+        CAMERA_HEIGHT,
+    );
+    shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
+    shadowGroup.add(shadowCamera);
+
+    cameraHelper = new THREE.CameraHelper(shadowCamera);
+
+    // like MeshDepthMaterial, but goes from black to transparent
+    depthMaterial = new THREE.MeshDepthMaterial();
+    depthMaterial.userData.darkness = { value: state.shadow.darkness };
+    depthMaterial.onBeforeCompile = function (shader) {
+        shader.uniforms.darkness = depthMaterial.userData.darkness;
+        shader.fragmentShader = /* glsl */ `
+						uniform float darkness;
+						${shader.fragmentShader.replace(
+                            'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
+                            'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );',
+                        )}
+					`;
+    };
+
+    depthMaterial.depthTest = false;
+    depthMaterial.depthWrite = false;
+
+    horizontalBlurMaterial = new THREE.ShaderMaterial(HorizontalBlurShader);
+    horizontalBlurMaterial.depthTest = false;
+
+    verticalBlurMaterial = new THREE.ShaderMaterial(VerticalBlurShader);
+    verticalBlurMaterial.depthTest = false;
+
+    //
+
+    gui = new GUI();
+    const shadowFolder = gui.addFolder('shadow');
+    shadowFolder.open();
+    const planeFolder = gui.addFolder('plane');
+    planeFolder.open();
+
+    shadowFolder.add(state.shadow, 'blur', 0, 15, 0.1);
+    shadowFolder.add(state.shadow, 'darkness', 1, 5, 0.1).onChange(function () {
+        depthMaterial.userData.darkness.value = state.shadow.darkness;
+    });
+    shadowFolder.add(state.shadow, 'opacity', 0, 1, 0.01).onChange(function () {
+        plane.material.opacity = state.shadow.opacity;
+    });
+    planeFolder.addColor(state.plane, 'color').onChange(function () {
+        fillPlane.material.color = new THREE.Color(state.plane.color);
+    });
+    planeFolder.add(state.plane, 'opacity', 0, 1, 0.01).onChange(function () {
+        fillPlane.material.opacity = state.plane.opacity;
+    });
+
+    gui.add(state, 'showWireframe').onChange(function () {
+        if (state.showWireframe) {
+            scene.add(cameraHelper);
+        } else {
+            scene.remove(cameraHelper);
+        }
+    });
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    new OrbitControls(camera, renderer.domElement);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+// renderTarget --> blurPlane (horizontalBlur) --> renderTargetBlur --> blurPlane (verticalBlur) --> renderTarget
+function blurShadow(amount) {
+    blurPlane.visible = true;
+
+    // blur horizontally and draw in the renderTargetBlur
+    blurPlane.material = horizontalBlurMaterial;
+    blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture;
+    horizontalBlurMaterial.uniforms.h.value = (amount * 1) / 256;
+
+    renderer.setRenderTarget(renderTargetBlur);
+    renderer.render(blurPlane, shadowCamera);
+
+    // blur vertically and draw in the main renderTarget
+    blurPlane.material = verticalBlurMaterial;
+    blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture;
+    verticalBlurMaterial.uniforms.v.value = (amount * 1) / 256;
+
+    renderer.setRenderTarget(renderTarget);
+    renderer.render(blurPlane, shadowCamera);
+
+    blurPlane.visible = false;
+}
+
+function animate() {
+    meshes.forEach(mesh => {
+        mesh.rotation.x += 0.01;
+        mesh.rotation.y += 0.02;
+    });
+
+    //
+
+    // remove the background
+    const initialBackground = scene.background;
+    scene.background = null;
+
+    // force the depthMaterial to everything
+    cameraHelper.visible = false;
+    scene.overrideMaterial = depthMaterial;
+
+    // set renderer clear alpha
+    const initialClearAlpha = renderer.getClearAlpha();
+    renderer.setClearAlpha(0);
+
+    // render to the render target to get the depths
+    renderer.setRenderTarget(renderTarget);
+    renderer.render(scene, shadowCamera);
+
+    // and reset the override material
+    scene.overrideMaterial = null;
+    cameraHelper.visible = true;
+
+    blurShadow(state.shadow.blur);
+
+    // a second pass to reduce the artifacts
+    // (0.4 is the minimum blur amout so that the artifacts are gone)
+    blurShadow(state.shadow.blur * 0.4);
+
+    // reset and render the normal scene
+    renderer.setRenderTarget(null);
+    renderer.setClearAlpha(initialClearAlpha);
+    scene.background = initialBackground;
+
+    renderer.render(scene, camera);
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_shadowmap.ts b/examples-testing/examples/webgl_shadowmap.ts
new file mode 100644
index 000000000..6d0ac3adb
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap.ts
@@ -0,0 +1,311 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js';
+
+const SHADOW_MAP_WIDTH = 2048,
+    SHADOW_MAP_HEIGHT = 1024;
+
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+const FLOOR = -250;
+
+let camera, controls, scene, renderer;
+let container, stats;
+
+const NEAR = 10,
+    FAR = 3000;
+
+let mixer;
+
+const morphs = [];
+
+let light;
+let lightShadowMapViewer;
+
+const clock = new THREE.Clock();
+
+let showHUD = false;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(23, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR);
+    camera.position.set(700, 50, 1900);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x59472b);
+    scene.fog = new THREE.Fog(0x59472b, 1000, FAR);
+
+    // LIGHTS
+
+    const ambient = new THREE.AmbientLight(0xffffff);
+    scene.add(ambient);
+
+    light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 1500, 1000);
+    light.castShadow = true;
+    light.shadow.camera.top = 2000;
+    light.shadow.camera.bottom = -2000;
+    light.shadow.camera.left = -2000;
+    light.shadow.camera.right = 2000;
+    light.shadow.camera.near = 1200;
+    light.shadow.camera.far = 2500;
+    light.shadow.bias = 0.0001;
+
+    light.shadow.mapSize.width = SHADOW_MAP_WIDTH;
+    light.shadow.mapSize.height = SHADOW_MAP_HEIGHT;
+
+    scene.add(light);
+
+    createHUD();
+    createScene();
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    renderer.autoClear = false;
+
+    //
+
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFShadowMap;
+
+    // CONTROLS
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+
+    controls.lookSpeed = 0.0125;
+    controls.movementSpeed = 500;
+    controls.lookVertical = true;
+
+    controls.lookAt(scene.position);
+
+    // STATS
+
+    stats = new Stats();
+    //container.appendChild( stats.dom );
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+    window.addEventListener('keydown', onKeyDown);
+}
+
+function onWindowResize() {
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    controls.handleResize();
+}
+
+function onKeyDown(event) {
+    switch (event.keyCode) {
+        case 84 /*t*/:
+            showHUD = !showHUD;
+            break;
+    }
+}
+
+function createHUD() {
+    lightShadowMapViewer = new ShadowMapViewer(light);
+    lightShadowMapViewer.position.x = 10;
+    lightShadowMapViewer.position.y = SCREEN_HEIGHT - SHADOW_MAP_HEIGHT / 4 - 10;
+    lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4;
+    lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4;
+    lightShadowMapViewer.update();
+}
+
+function createScene() {
+    // GROUND
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+    const planeMaterial = new THREE.MeshPhongMaterial({ color: 0xffdd99 });
+
+    const ground = new THREE.Mesh(geometry, planeMaterial);
+
+    ground.position.set(0, FLOOR, 0);
+    ground.rotation.x = -Math.PI / 2;
+    ground.scale.set(100, 100, 100);
+
+    ground.castShadow = false;
+    ground.receiveShadow = true;
+
+    scene.add(ground);
+
+    // TEXT
+
+    const loader = new FontLoader();
+    loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
+        const textGeo = new TextGeometry('THREE.JS', {
+            font: font,
+
+            size: 200,
+            depth: 50,
+            curveSegments: 12,
+
+            bevelThickness: 2,
+            bevelSize: 5,
+            bevelEnabled: true,
+        });
+
+        textGeo.computeBoundingBox();
+        const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
+
+        const textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, specular: 0xffffff });
+
+        const mesh = new THREE.Mesh(textGeo, textMaterial);
+        mesh.position.x = centerOffset;
+        mesh.position.y = FLOOR + 67;
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+    });
+
+    // CUBES
+
+    const cubes1 = new THREE.Mesh(new THREE.BoxGeometry(1500, 220, 150), planeMaterial);
+
+    cubes1.position.y = FLOOR - 50;
+    cubes1.position.z = 20;
+
+    cubes1.castShadow = true;
+    cubes1.receiveShadow = true;
+
+    scene.add(cubes1);
+
+    const cubes2 = new THREE.Mesh(new THREE.BoxGeometry(1600, 170, 250), planeMaterial);
+
+    cubes2.position.y = FLOOR - 50;
+    cubes2.position.z = 20;
+
+    cubes2.castShadow = true;
+    cubes2.receiveShadow = true;
+
+    scene.add(cubes2);
+
+    // MORPHS
+
+    mixer = new THREE.AnimationMixer(scene);
+
+    function addMorph(mesh, clip, speed, duration, x, y, z, fudgeColor) {
+        mesh = mesh.clone();
+        mesh.material = mesh.material.clone();
+
+        if (fudgeColor) {
+            mesh.material.color.offsetHSL(0, Math.random() * 0.5 - 0.25, Math.random() * 0.5 - 0.25);
+        }
+
+        mesh.speed = speed;
+
+        mixer
+            .clipAction(clip, mesh)
+            .setDuration(duration)
+            // to shift the playback out of phase:
+            .startAt(-duration * Math.random())
+            .play();
+
+        mesh.position.set(x, y, z);
+        mesh.rotation.y = Math.PI / 2;
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+
+        morphs.push(mesh);
+    }
+
+    const gltfloader = new GLTFLoader();
+
+    gltfloader.load('models/gltf/Horse.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+
+        const clip = gltf.animations[0];
+
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 300, true);
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 450, true);
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 600, true);
+
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -300, true);
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -450, true);
+        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -600, true);
+    });
+
+    gltfloader.load('models/gltf/Flamingo.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+        const clip = gltf.animations[0];
+
+        addMorph(mesh, clip, 500, 1, 500 - Math.random() * 500, FLOOR + 350, 40);
+    });
+
+    gltfloader.load('models/gltf/Stork.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+        const clip = gltf.animations[0];
+
+        addMorph(mesh, clip, 350, 1, 500 - Math.random() * 500, FLOOR + 350, 340);
+    });
+
+    gltfloader.load('models/gltf/Parrot.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+        const clip = gltf.animations[0];
+
+        addMorph(mesh, clip, 450, 0.5, 500 - Math.random() * 500, FLOOR + 300, 700);
+    });
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    mixer.update(delta);
+
+    for (let i = 0; i < morphs.length; i++) {
+        const morph = morphs[i];
+
+        morph.position.x += morph.speed * delta;
+
+        if (morph.position.x > 2000) {
+            morph.position.x = -1000 - Math.random() * 500;
+        }
+    }
+
+    controls.update(delta);
+
+    renderer.clear();
+    renderer.render(scene, camera);
+
+    // Render debug HUD with shadow map
+
+    if (showHUD) {
+        lightShadowMapViewer.render(renderer);
+    }
+}
diff --git a/examples-testing/examples/webgl_shadowmap_csm.ts b/examples-testing/examples/webgl_shadowmap_csm.ts
new file mode 100644
index 000000000..c89bc02df
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_csm.ts
@@ -0,0 +1,253 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { CSM } from 'three/addons/csm/CSM.js';
+import { CSMHelper } from 'three/addons/csm/CSMHelper.js';
+
+let renderer, scene, camera, orthoCamera, controls, csm, csmHelper;
+
+const params = {
+    orthographic: false,
+    fade: false,
+    shadows: true,
+    far: 1000,
+    mode: 'practical',
+    lightX: -1,
+    lightY: -1,
+    lightZ: -1,
+    margin: 100,
+    lightFar: 5000,
+    lightNear: 1,
+    autoUpdateHelper: true,
+    updateHelper: function () {
+        csmHelper.update();
+    },
+};
+
+init();
+
+function updateOrthoCamera() {
+    const size = controls.target.distanceTo(camera.position);
+    const aspect = camera.aspect;
+
+    orthoCamera.left = (size * aspect) / -2;
+    orthoCamera.right = (size * aspect) / 2;
+
+    orthoCamera.top = size / 2;
+    orthoCamera.bottom = size / -2;
+    orthoCamera.position.copy(camera.position);
+    orthoCamera.rotation.copy(camera.rotation);
+    orthoCamera.updateProjectionMatrix();
+}
+
+function init() {
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color('#454e61');
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 5000);
+    orthoCamera = new THREE.OrthographicCamera();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+    renderer.shadowMap.enabled = params.shadows;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI / 2;
+    camera.position.set(60, 60, 0);
+    controls.target = new THREE.Vector3(-100, 10, 0);
+    controls.update();
+
+    const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
+    scene.add(ambientLight);
+
+    const additionalDirectionalLight = new THREE.DirectionalLight(0x000020, 1.5);
+    additionalDirectionalLight.position
+        .set(params.lightX, params.lightY, params.lightZ)
+        .normalize()
+        .multiplyScalar(-200);
+    scene.add(additionalDirectionalLight);
+
+    csm = new CSM({
+        maxFar: params.far,
+        cascades: 4,
+        mode: params.mode,
+        parent: scene,
+        shadowMapSize: 1024,
+        lightDirection: new THREE.Vector3(params.lightX, params.lightY, params.lightZ).normalize(),
+        camera: camera,
+    });
+
+    csmHelper = new CSMHelper(csm);
+    csmHelper.visible = false;
+    scene.add(csmHelper);
+
+    const floorMaterial = new THREE.MeshPhongMaterial({ color: '#252a34' });
+    csm.setupMaterial(floorMaterial);
+
+    const floor = new THREE.Mesh(new THREE.PlaneGeometry(10000, 10000, 8, 8), floorMaterial);
+    floor.rotation.x = -Math.PI / 2;
+    floor.castShadow = true;
+    floor.receiveShadow = true;
+    scene.add(floor);
+
+    const material1 = new THREE.MeshPhongMaterial({ color: '#08d9d6' });
+    csm.setupMaterial(material1);
+
+    const material2 = new THREE.MeshPhongMaterial({ color: '#ff2e63' });
+    csm.setupMaterial(material2);
+
+    const geometry = new THREE.BoxGeometry(10, 10, 10);
+
+    for (let i = 0; i < 40; i++) {
+        const cube1 = new THREE.Mesh(geometry, i % 2 === 0 ? material1 : material2);
+        cube1.castShadow = true;
+        cube1.receiveShadow = true;
+        scene.add(cube1);
+        cube1.position.set(-i * 25, 20, 30);
+        cube1.scale.y = Math.random() * 2 + 6;
+
+        const cube2 = new THREE.Mesh(geometry, i % 2 === 0 ? material2 : material1);
+        cube2.castShadow = true;
+        cube2.receiveShadow = true;
+        scene.add(cube2);
+        cube2.position.set(-i * 25, 20, -30);
+        cube2.scale.y = Math.random() * 2 + 6;
+    }
+
+    const gui = new GUI();
+
+    gui.add(params, 'orthographic').onChange(function (value) {
+        csm.camera = value ? orthoCamera : camera;
+        csm.updateFrustums();
+    });
+
+    gui.add(params, 'fade').onChange(function (value) {
+        csm.fade = value;
+        csm.updateFrustums();
+    });
+
+    gui.add(params, 'shadows').onChange(function (value) {
+        renderer.shadowMap.enabled = value;
+
+        scene.traverse(function (child) {
+            if (child.material) {
+                child.material.needsUpdate = true;
+            }
+        });
+    });
+
+    gui.add(params, 'far', 1, 5000)
+        .step(1)
+        .name('shadow far')
+        .onChange(function (value) {
+            csm.maxFar = value;
+            csm.updateFrustums();
+        });
+
+    gui.add(params, 'mode', ['uniform', 'logarithmic', 'practical'])
+        .name('frustum split mode')
+        .onChange(function (value) {
+            csm.mode = value;
+            csm.updateFrustums();
+        });
+
+    gui.add(params, 'lightX', -1, 1)
+        .name('light direction x')
+        .onChange(function (value) {
+            csm.lightDirection.x = value;
+        });
+
+    gui.add(params, 'lightY', -1, 1)
+        .name('light direction y')
+        .onChange(function (value) {
+            csm.lightDirection.y = value;
+        });
+
+    gui.add(params, 'lightZ', -1, 1)
+        .name('light direction z')
+        .onChange(function (value) {
+            csm.lightDirection.z = value;
+        });
+
+    gui.add(params, 'margin', 0, 200)
+        .name('light margin')
+        .onChange(function (value) {
+            csm.lightMargin = value;
+        });
+
+    gui.add(params, 'lightNear', 1, 10000)
+        .name('light near')
+        .onChange(function (value) {
+            for (let i = 0; i < csm.lights.length; i++) {
+                csm.lights[i].shadow.camera.near = value;
+                csm.lights[i].shadow.camera.updateProjectionMatrix();
+            }
+        });
+
+    gui.add(params, 'lightFar', 1, 10000)
+        .name('light far')
+        .onChange(function (value) {
+            for (let i = 0; i < csm.lights.length; i++) {
+                csm.lights[i].shadow.camera.far = value;
+                csm.lights[i].shadow.camera.updateProjectionMatrix();
+            }
+        });
+
+    const helperFolder = gui.addFolder('helper');
+
+    helperFolder.add(csmHelper, 'visible');
+
+    helperFolder.add(csmHelper, 'displayFrustum').onChange(function () {
+        csmHelper.updateVisibility();
+    });
+
+    helperFolder.add(csmHelper, 'displayPlanes').onChange(function () {
+        csmHelper.updateVisibility();
+    });
+
+    helperFolder.add(csmHelper, 'displayShadowBounds').onChange(function () {
+        csmHelper.updateVisibility();
+    });
+
+    helperFolder.add(params, 'autoUpdateHelper').name('auto update');
+
+    helperFolder.add(params, 'updateHelper').name('update');
+
+    helperFolder.open();
+
+    window.addEventListener('resize', function () {
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+
+        updateOrthoCamera();
+        csm.updateFrustums();
+
+        renderer.setSize(window.innerWidth, window.innerHeight);
+    });
+}
+
+function animate() {
+    camera.updateMatrixWorld();
+    csm.update();
+    controls.update();
+
+    if (params.orthographic) {
+        updateOrthoCamera();
+        csm.updateFrustums();
+
+        if (params.autoUpdateHelper) {
+            csmHelper.update();
+        }
+
+        renderer.render(scene, orthoCamera);
+    } else {
+        if (params.autoUpdateHelper) {
+            csmHelper.update();
+        }
+
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgl_shadowmap_pcss.ts b/examples-testing/examples/webgl_shadowmap_pcss.ts
new file mode 100644
index 000000000..a47a011ff
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_pcss.ts
@@ -0,0 +1,161 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let stats;
+let camera, scene, renderer;
+
+let group;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0xcce0ff, 5, 100);
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
+
+    // We use this particular camera position in order to expose a bug that can sometimes happen presumably
+    // due to lack of precision when interpolating values over really large triangles.
+    // It reproduced on at least NVIDIA GTX 1080 and GTX 1050 Ti GPUs when the ground plane was not
+    // subdivided into segments.
+    camera.position.x = 7;
+    camera.position.y = 13;
+    camera.position.z = 7;
+
+    scene.add(camera);
+
+    // lights
+
+    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
+
+    const light = new THREE.DirectionalLight(0xf0f6ff, 4.5);
+    light.position.set(2, 8, 4);
+
+    light.castShadow = true;
+    light.shadow.mapSize.width = 1024;
+    light.shadow.mapSize.height = 1024;
+    light.shadow.camera.far = 20;
+
+    scene.add(light);
+
+    // scene.add( new DirectionalLightHelper( light ) );
+    scene.add(new THREE.CameraHelper(light.shadow.camera));
+
+    // group
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const geometry = new THREE.SphereGeometry(0.3, 20, 20);
+
+    for (let i = 0; i < 20; i++) {
+        const material = new THREE.MeshPhongMaterial({ color: Math.random() * 0xffffff });
+
+        const sphere = new THREE.Mesh(geometry, material);
+        sphere.position.x = Math.random() - 0.5;
+        sphere.position.z = Math.random() - 0.5;
+        sphere.position.normalize();
+        sphere.position.multiplyScalar(Math.random() * 2 + 1);
+        sphere.castShadow = true;
+        sphere.receiveShadow = true;
+        sphere.userData.phase = Math.random() * Math.PI;
+        group.add(sphere);
+    }
+
+    // ground
+
+    const groundMaterial = new THREE.MeshPhongMaterial({ color: 0x898989 });
+
+    const ground = new THREE.Mesh(new THREE.PlaneGeometry(20000, 20000, 8, 8), groundMaterial);
+    ground.rotation.x = -Math.PI / 2;
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // column
+
+    const column = new THREE.Mesh(new THREE.BoxGeometry(1, 4, 1), groundMaterial);
+    column.position.y = 2;
+    column.castShadow = true;
+    column.receiveShadow = true;
+    scene.add(column);
+
+    // overwrite shadowmap code
+
+    let shader = THREE.ShaderChunk.shadowmap_pars_fragment;
+
+    shader = shader.replace(
+        '#ifdef USE_SHADOWMAP',
+        '#ifdef USE_SHADOWMAP' + document.getElementById('PCSS').textContent,
+    );
+
+    shader = shader.replace(
+        '#if defined( SHADOWMAP_TYPE_PCF )',
+        document.getElementById('PCSSGetShadow').textContent + '#if defined( SHADOWMAP_TYPE_PCF )',
+    );
+
+    THREE.ShaderChunk.shadowmap_pars_fragment = shader;
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.setClearColor(scene.fog.color);
+
+    container.appendChild(renderer.domElement);
+
+    renderer.shadowMap.enabled = true;
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI * 0.5;
+    controls.minDistance = 10;
+    controls.maxDistance = 75;
+    controls.target.set(0, 2.5, 0);
+    controls.update();
+
+    // performance monitor
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = performance.now() / 1000;
+
+    group.traverse(function (child) {
+        if ('phase' in child.userData) {
+            child.position.y = Math.abs(Math.sin(time + child.userData.phase)) * 4 + 0.3;
+        }
+    });
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_shadowmap_performance.ts b/examples-testing/examples/webgl_shadowmap_performance.ts
new file mode 100644
index 000000000..0e45b63f9
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_performance.ts
@@ -0,0 +1,281 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+const SHADOW_MAP_WIDTH = 2048,
+    SHADOW_MAP_HEIGHT = 1024;
+
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+const FLOOR = -250;
+
+const ANIMATION_GROUPS = 25;
+
+let camera, controls, scene, renderer;
+let stats;
+
+const NEAR = 5,
+    FAR = 3000;
+
+let morph, mixer;
+
+const morphs = [],
+    animGroups = [];
+
+const clock = new THREE.Clock();
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(23, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR);
+    camera.position.set(700, 50, 1900);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x59472b);
+    scene.fog = new THREE.Fog(0x59472b, 1000, FAR);
+
+    // LIGHTS
+
+    const ambient = new THREE.AmbientLight(0xffffff);
+    scene.add(ambient);
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 1500, 1000);
+    light.castShadow = true;
+    light.shadow.camera.top = 2000;
+    light.shadow.camera.bottom = -2000;
+    light.shadow.camera.left = -2000;
+    light.shadow.camera.right = 2000;
+    light.shadow.camera.near = 1200;
+    light.shadow.camera.far = 2500;
+    light.shadow.bias = 0.0001;
+
+    light.shadow.mapSize.width = SHADOW_MAP_WIDTH;
+    light.shadow.mapSize.height = SHADOW_MAP_HEIGHT;
+
+    scene.add(light);
+
+    createScene();
+
+    // RENDERER
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    renderer.autoClear = false;
+
+    //
+
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+    // CONTROLS
+
+    controls = new FirstPersonControls(camera, renderer.domElement);
+
+    controls.lookSpeed = 0.0125;
+    controls.movementSpeed = 500;
+    controls.lookVertical = true;
+
+    controls.lookAt(scene.position);
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    controls.handleResize();
+}
+
+function createScene() {
+    // GROUND
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+    const planeMaterial = new THREE.MeshPhongMaterial({ color: 0xffdd99 });
+
+    const ground = new THREE.Mesh(geometry, planeMaterial);
+
+    ground.position.set(0, FLOOR, 0);
+    ground.rotation.x = -Math.PI / 2;
+    ground.scale.set(100, 100, 100);
+
+    ground.castShadow = false;
+    ground.receiveShadow = true;
+
+    scene.add(ground);
+
+    // TEXT
+
+    const loader = new FontLoader();
+    loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
+        const textGeo = new TextGeometry('THREE.JS', {
+            font: font,
+
+            size: 200,
+            depth: 50,
+            curveSegments: 12,
+
+            bevelThickness: 2,
+            bevelSize: 5,
+            bevelEnabled: true,
+        });
+
+        textGeo.computeBoundingBox();
+        const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
+
+        const textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, specular: 0xffffff });
+
+        const mesh = new THREE.Mesh(textGeo, textMaterial);
+        mesh.position.x = centerOffset;
+        mesh.position.y = FLOOR + 67;
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+    });
+
+    // CUBES
+
+    const cubes1 = new THREE.Mesh(new THREE.BoxGeometry(1500, 220, 150), planeMaterial);
+
+    cubes1.position.y = FLOOR - 50;
+    cubes1.position.z = 20;
+
+    cubes1.castShadow = true;
+    cubes1.receiveShadow = true;
+
+    scene.add(cubes1);
+
+    const cubes2 = new THREE.Mesh(new THREE.BoxGeometry(1600, 170, 250), planeMaterial);
+
+    cubes2.position.y = FLOOR - 50;
+    cubes2.position.z = 20;
+
+    cubes2.castShadow = true;
+    cubes2.receiveShadow = true;
+
+    scene.add(cubes2);
+
+    mixer = new THREE.AnimationMixer(scene);
+
+    for (let i = 0; i !== ANIMATION_GROUPS; ++i) {
+        const group = new THREE.AnimationObjectGroup();
+        animGroups.push(group);
+    }
+
+    // MORPHS
+
+    function addMorph(mesh, clip, speed, duration, x, y, z, fudgeColor, massOptimization) {
+        mesh = mesh.clone();
+        mesh.material = mesh.material.clone();
+
+        if (fudgeColor) {
+            mesh.material.color.offsetHSL(0, Math.random() * 0.5 - 0.25, Math.random() * 0.5 - 0.25);
+        }
+
+        mesh.speed = speed;
+
+        if (massOptimization) {
+            const index = Math.floor(Math.random() * ANIMATION_GROUPS),
+                animGroup = animGroups[index];
+
+            animGroup.add(mesh);
+
+            if (!mixer.existingAction(clip, animGroup)) {
+                const randomness = 0.6 * Math.random() - 0.3;
+                const phase = (index + randomness) / ANIMATION_GROUPS;
+
+                mixer
+                    .clipAction(clip, animGroup)
+                    .setDuration(duration)
+                    .startAt(-duration * phase)
+                    .play();
+            }
+        } else {
+            mixer
+                .clipAction(clip, mesh)
+                .setDuration(duration)
+                .startAt(-duration * Math.random())
+                .play();
+        }
+
+        mesh.position.set(x, y, z);
+        mesh.rotation.y = Math.PI / 2;
+
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+
+        scene.add(mesh);
+
+        morphs.push(mesh);
+    }
+
+    const gltfLoader = new GLTFLoader();
+    gltfLoader.load('models/gltf/Horse.glb', function (gltf) {
+        const mesh = gltf.scene.children[0];
+        const clip = gltf.animations[0];
+
+        for (let i = -600; i < 601; i += 2) {
+            addMorph(mesh, clip, 550, 1, 100 - Math.random() * 3000, FLOOR, i, true, true);
+        }
+    });
+}
+
+//
+
+function animate() {
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    if (mixer) mixer.update(delta);
+
+    for (let i = 0; i < morphs.length; i++) {
+        morph = morphs[i];
+
+        morph.position.x += morph.speed * delta;
+
+        if (morph.position.x > 2000) {
+            morph.position.x = -1000 - Math.random() * 500;
+        }
+    }
+
+    controls.update(delta);
+
+    renderer.clear();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shadowmap_pointlight.ts b/examples-testing/examples/webgl_shadowmap_pointlight.ts
new file mode 100644
index 000000000..c68d69749
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_pointlight.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, stats;
+let pointLight, pointLight2;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 10, 40);
+
+    scene = new THREE.Scene();
+    scene.add(new THREE.AmbientLight(0x111122, 3));
+
+    // lights
+
+    function createLight(color) {
+        const intensity = 200;
+
+        const light = new THREE.PointLight(color, intensity, 20);
+        light.castShadow = true;
+        light.shadow.bias = -0.005; // reduces self-shadowing on double-sided objects
+
+        let geometry = new THREE.SphereGeometry(0.3, 12, 6);
+        let material = new THREE.MeshBasicMaterial({ color: color });
+        material.color.multiplyScalar(intensity);
+        let sphere = new THREE.Mesh(geometry, material);
+        light.add(sphere);
+
+        const texture = new THREE.CanvasTexture(generateTexture());
+        texture.magFilter = THREE.NearestFilter;
+        texture.wrapT = THREE.RepeatWrapping;
+        texture.wrapS = THREE.RepeatWrapping;
+        texture.repeat.set(1, 4.5);
+
+        geometry = new THREE.SphereGeometry(2, 32, 8);
+        material = new THREE.MeshPhongMaterial({
+            side: THREE.DoubleSide,
+            alphaMap: texture,
+            alphaTest: 0.5,
+        });
+
+        sphere = new THREE.Mesh(geometry, material);
+        sphere.castShadow = true;
+        sphere.receiveShadow = true;
+        light.add(sphere);
+
+        return light;
+    }
+
+    pointLight = createLight(0x0088ff);
+    scene.add(pointLight);
+
+    pointLight2 = createLight(0xff8888);
+    scene.add(pointLight2);
+    //
+
+    const geometry = new THREE.BoxGeometry(30, 30, 30);
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xa0adaf,
+        shininess: 10,
+        specular: 0x111111,
+        side: THREE.BackSide,
+    });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    mesh.position.y = 10;
+    mesh.receiveShadow = true;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.BasicShadowMap;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 10, 0);
+    controls.update();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function generateTexture() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 2;
+    canvas.height = 2;
+
+    const context = canvas.getContext('2d');
+    context.fillStyle = 'white';
+    context.fillRect(0, 1, 2, 1);
+
+    return canvas;
+}
+
+function animate() {
+    let time = performance.now() * 0.001;
+
+    pointLight.position.x = Math.sin(time * 0.6) * 9;
+    pointLight.position.y = Math.sin(time * 0.7) * 9 + 6;
+    pointLight.position.z = Math.sin(time * 0.8) * 9;
+
+    pointLight.rotation.x = time;
+    pointLight.rotation.z = time;
+
+    time += 10000;
+
+    pointLight2.position.x = Math.sin(time * 0.6) * 9;
+    pointLight2.position.y = Math.sin(time * 0.7) * 9 + 6;
+    pointLight2.position.z = Math.sin(time * 0.8) * 9;
+
+    pointLight2.rotation.x = time;
+    pointLight2.rotation.z = time;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_shadowmap_progressive.ts b/examples-testing/examples/webgl_shadowmap_progressive.ts
new file mode 100644
index 000000000..86ec68172
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_progressive.ts
@@ -0,0 +1,204 @@
+import * as THREE from 'three';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { TransformControls } from 'three/addons/controls/TransformControls.js';
+import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMap.js';
+
+// ShadowMap + LightMap Res and Number of Directional Lights
+const shadowMapRes = 512,
+    lightMapRes = 1024,
+    lightCount = 8;
+let camera,
+    scene,
+    renderer,
+    controls,
+    control,
+    control2,
+    object = new THREE.Mesh(),
+    lightOrigin = null,
+    progressiveSurfacemap;
+const dirLights = [],
+    lightmapObjects = [];
+const params = {
+    Enable: true,
+    'Blur Edges': true,
+    'Blend Window': 200,
+    'Light Radius': 50,
+    'Ambient Weight': 0.5,
+    'Debug Lightmap': false,
+};
+init();
+createGUI();
+
+function init() {
+    // renderer
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    document.body.appendChild(renderer.domElement);
+
+    // camera
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 100, 200);
+    camera.name = 'Camera';
+
+    // scene
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x949494);
+    scene.fog = new THREE.Fog(0x949494, 1000, 3000);
+
+    // progressive lightmap
+    progressiveSurfacemap = new ProgressiveLightMap(renderer, lightMapRes);
+
+    // directional lighting "origin"
+    lightOrigin = new THREE.Group();
+    lightOrigin.position.set(60, 150, 100);
+    scene.add(lightOrigin);
+
+    // transform gizmo
+    control = new TransformControls(camera, renderer.domElement);
+    control.addEventListener('dragging-changed', event => {
+        controls.enabled = !event.value;
+    });
+    control.attach(lightOrigin);
+    scene.add(control);
+
+    // create 8 directional lights to speed up the convergence
+    for (let l = 0; l < lightCount; l++) {
+        const dirLight = new THREE.DirectionalLight(0xffffff, 1.0 / lightCount);
+        dirLight.name = 'Dir. Light ' + l;
+        dirLight.position.set(200, 200, 200);
+        dirLight.castShadow = true;
+        dirLight.shadow.camera.near = 100;
+        dirLight.shadow.camera.far = 5000;
+        dirLight.shadow.camera.right = 150;
+        dirLight.shadow.camera.left = -150;
+        dirLight.shadow.camera.top = 150;
+        dirLight.shadow.camera.bottom = -150;
+        dirLight.shadow.mapSize.width = shadowMapRes;
+        dirLight.shadow.mapSize.height = shadowMapRes;
+        lightmapObjects.push(dirLight);
+        dirLights.push(dirLight);
+    }
+
+    // ground
+    const groundMesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(600, 600),
+        new THREE.MeshPhongMaterial({ color: 0xffffff, depthWrite: true }),
+    );
+    groundMesh.position.y = -0.1;
+    groundMesh.rotation.x = -Math.PI / 2;
+    groundMesh.name = 'Ground Mesh';
+    lightmapObjects.push(groundMesh);
+    scene.add(groundMesh);
+
+    // model
+    function loadModel() {
+        object.traverse(function (child) {
+            if (child.isMesh) {
+                child.name = 'Loaded Mesh';
+                child.castShadow = true;
+                child.receiveShadow = true;
+                child.material = new THREE.MeshPhongMaterial();
+
+                // This adds the model to the lightmap
+                lightmapObjects.push(child);
+                progressiveSurfacemap.addObjectsToLightMap(lightmapObjects);
+            } else {
+                child.layers.disableAll(); // Disable Rendering for this
+            }
+        });
+        scene.add(object);
+        object.scale.set(2, 2, 2);
+        object.position.set(0, -16, 0);
+        control2 = new TransformControls(camera, renderer.domElement);
+        control2.addEventListener('dragging-changed', event => {
+            controls.enabled = !event.value;
+        });
+        control2.attach(object);
+        scene.add(control2);
+        const lightTarget = new THREE.Group();
+        lightTarget.position.set(0, 20, 0);
+        for (let l = 0; l < dirLights.length; l++) {
+            dirLights[l].target = lightTarget;
+        }
+
+        object.add(lightTarget);
+    }
+
+    const manager = new THREE.LoadingManager(loadModel);
+    const loader = new GLTFLoader(manager);
+    loader.load('models/gltf/ShadowmappableMesh.glb', function (obj) {
+        object = obj.scene.children[0];
+    });
+
+    // controls
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
+    controls.dampingFactor = 0.05;
+    controls.screenSpacePanning = true;
+    controls.minDistance = 100;
+    controls.maxDistance = 500;
+    controls.maxPolarAngle = Math.PI / 1.5;
+    controls.target.set(0, 100, 0);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createGUI() {
+    const gui = new GUI({ title: 'Accumulation Settings' });
+    gui.add(params, 'Enable');
+    gui.add(params, 'Blur Edges');
+    gui.add(params, 'Blend Window', 1, 500).step(1);
+    gui.add(params, 'Light Radius', 0, 200).step(10);
+    gui.add(params, 'Ambient Weight', 0, 1).step(0.1);
+    gui.add(params, 'Debug Lightmap');
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    // Update the inertia on the orbit controls
+    controls.update();
+
+    // Accumulate Surface Maps
+    if (params['Enable']) {
+        progressiveSurfacemap.update(camera, params['Blend Window'], params['Blur Edges']);
+
+        if (!progressiveSurfacemap.firstUpdate) {
+            progressiveSurfacemap.showDebugLightmap(params['Debug Lightmap']);
+        }
+    }
+
+    // Manually Update the Directional Lights
+    for (let l = 0; l < dirLights.length; l++) {
+        // Sometimes they will be sampled from the target direction
+        // Sometimes they will be uniformly sampled from the upper hemisphere
+        if (Math.random() > params['Ambient Weight']) {
+            dirLights[l].position.set(
+                lightOrigin.position.x + Math.random() * params['Light Radius'],
+                lightOrigin.position.y + Math.random() * params['Light Radius'],
+                lightOrigin.position.z + Math.random() * params['Light Radius'],
+            );
+        } else {
+            // Uniform Hemispherical Surface Distribution for Ambient Occlusion
+            const lambda = Math.acos(2 * Math.random() - 1) - 3.14159 / 2.0;
+            const phi = 2 * 3.14159 * Math.random();
+            dirLights[l].position.set(
+                Math.cos(lambda) * Math.cos(phi) * 300 + object.position.x,
+                Math.abs(Math.cos(lambda) * Math.sin(phi) * 300) + object.position.y + 20,
+                Math.sin(lambda) * 300 + object.position.z,
+            );
+        }
+    }
+
+    // Render Scene
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_shadowmap_viewer.ts b/examples-testing/examples/webgl_shadowmap_viewer.ts
new file mode 100644
index 000000000..f974ef038
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_viewer.ts
@@ -0,0 +1,178 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js';
+
+let camera, scene, renderer, clock, stats;
+let dirLight, spotLight;
+let torusKnot, cube;
+let dirLightShadowMapViewer, spotLightShadowMapViewer;
+
+init();
+
+function init() {
+    initScene();
+    initShadowMapViewers();
+    initMisc();
+
+    document.body.appendChild(renderer.domElement);
+    window.addEventListener('resize', onWindowResize);
+}
+
+function initScene() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 15, 35);
+
+    scene = new THREE.Scene();
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0x404040, 3));
+
+    spotLight = new THREE.SpotLight(0xffffff, 500);
+    spotLight.name = 'Spot Light';
+    spotLight.angle = Math.PI / 5;
+    spotLight.penumbra = 0.3;
+    spotLight.position.set(10, 10, 5);
+    spotLight.castShadow = true;
+    spotLight.shadow.camera.near = 8;
+    spotLight.shadow.camera.far = 30;
+    spotLight.shadow.mapSize.width = 1024;
+    spotLight.shadow.mapSize.height = 1024;
+    scene.add(spotLight);
+
+    scene.add(new THREE.CameraHelper(spotLight.shadow.camera));
+
+    dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.name = 'Dir. Light';
+    dirLight.position.set(0, 10, 0);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.near = 1;
+    dirLight.shadow.camera.far = 10;
+    dirLight.shadow.camera.right = 15;
+    dirLight.shadow.camera.left = -15;
+    dirLight.shadow.camera.top = 15;
+    dirLight.shadow.camera.bottom = -15;
+    dirLight.shadow.mapSize.width = 1024;
+    dirLight.shadow.mapSize.height = 1024;
+    scene.add(dirLight);
+
+    scene.add(new THREE.CameraHelper(dirLight.shadow.camera));
+
+    // Geometry
+    let geometry = new THREE.TorusKnotGeometry(25, 8, 75, 20);
+    let material = new THREE.MeshPhongMaterial({
+        color: 0xff0000,
+        shininess: 150,
+        specular: 0x222222,
+    });
+
+    torusKnot = new THREE.Mesh(geometry, material);
+    torusKnot.scale.multiplyScalar(1 / 18);
+    torusKnot.position.y = 3;
+    torusKnot.castShadow = true;
+    torusKnot.receiveShadow = true;
+    scene.add(torusKnot);
+
+    geometry = new THREE.BoxGeometry(3, 3, 3);
+    cube = new THREE.Mesh(geometry, material);
+    cube.position.set(8, 3, 8);
+    cube.castShadow = true;
+    cube.receiveShadow = true;
+    scene.add(cube);
+
+    geometry = new THREE.BoxGeometry(10, 0.15, 10);
+    material = new THREE.MeshPhongMaterial({
+        color: 0xa0adaf,
+        shininess: 150,
+        specular: 0x111111,
+    });
+
+    const ground = new THREE.Mesh(geometry, material);
+    ground.scale.multiplyScalar(3);
+    ground.castShadow = false;
+    ground.receiveShadow = true;
+    scene.add(ground);
+}
+
+function initShadowMapViewers() {
+    dirLightShadowMapViewer = new ShadowMapViewer(dirLight);
+    spotLightShadowMapViewer = new ShadowMapViewer(spotLight);
+    resizeShadowMapViewers();
+}
+
+function initMisc() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.BasicShadowMap;
+
+    // Mouse control
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 2, 0);
+    controls.update();
+
+    clock = new THREE.Clock();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+}
+
+function resizeShadowMapViewers() {
+    const size = window.innerWidth * 0.15;
+
+    dirLightShadowMapViewer.position.x = 10;
+    dirLightShadowMapViewer.position.y = 10;
+    dirLightShadowMapViewer.size.width = size;
+    dirLightShadowMapViewer.size.height = size;
+    dirLightShadowMapViewer.update(); //Required when setting position or size directly
+
+    spotLightShadowMapViewer.size.set(size, size);
+    spotLightShadowMapViewer.position.set(size + 20, 10);
+    // spotLightShadowMapViewer.update();	//NOT required because .set updates automatically
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    resizeShadowMapViewers();
+    dirLightShadowMapViewer.updateForWindowResize();
+    spotLightShadowMapViewer.updateForWindowResize();
+}
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function renderScene() {
+    renderer.render(scene, camera);
+}
+
+function renderShadowMapViewers() {
+    dirLightShadowMapViewer.render(renderer);
+    spotLightShadowMapViewer.render(renderer);
+}
+
+function render() {
+    const delta = clock.getDelta();
+
+    renderScene();
+    renderShadowMapViewers();
+
+    torusKnot.rotation.x += 0.25 * delta;
+    torusKnot.rotation.y += 2 * delta;
+    torusKnot.rotation.z += 1 * delta;
+
+    cube.rotation.x += 0.25 * delta;
+    cube.rotation.y += 2 * delta;
+    cube.rotation.z += 1 * delta;
+}
diff --git a/examples-testing/examples/webgl_shadowmap_vsm.ts b/examples-testing/examples/webgl_shadowmap_vsm.ts
new file mode 100644
index 000000000..4867c7315
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmap_vsm.ts
@@ -0,0 +1,200 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, clock, stats;
+let dirLight, spotLight;
+let torusKnot, dirGroup;
+
+init();
+
+function init() {
+    initScene();
+    initMisc();
+
+    // Init gui
+    const gui = new GUI();
+
+    const config = {
+        spotlightRadius: 4,
+        spotlightSamples: 8,
+        dirlightRadius: 4,
+        dirlightSamples: 8,
+    };
+
+    const spotlightFolder = gui.addFolder('Spotlight');
+    spotlightFolder
+        .add(config, 'spotlightRadius')
+        .name('radius')
+        .min(0)
+        .max(25)
+        .onChange(function (value) {
+            spotLight.shadow.radius = value;
+        });
+
+    spotlightFolder
+        .add(config, 'spotlightSamples', 1, 25, 1)
+        .name('samples')
+        .onChange(function (value) {
+            spotLight.shadow.blurSamples = value;
+        });
+    spotlightFolder.open();
+
+    const dirlightFolder = gui.addFolder('Directional Light');
+    dirlightFolder
+        .add(config, 'dirlightRadius')
+        .name('radius')
+        .min(0)
+        .max(25)
+        .onChange(function (value) {
+            dirLight.shadow.radius = value;
+        });
+
+    dirlightFolder
+        .add(config, 'dirlightSamples', 1, 25, 1)
+        .name('samples')
+        .onChange(function (value) {
+            dirLight.shadow.blurSamples = value;
+        });
+    dirlightFolder.open();
+
+    document.body.appendChild(renderer.domElement);
+    window.addEventListener('resize', onWindowResize);
+}
+
+function initScene() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 10, 30);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x222244);
+    scene.fog = new THREE.Fog(0x222244, 50, 100);
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0x444444));
+
+    spotLight = new THREE.SpotLight(0xff8888, 400);
+    spotLight.angle = Math.PI / 5;
+    spotLight.penumbra = 0.3;
+    spotLight.position.set(8, 10, 5);
+    spotLight.castShadow = true;
+    spotLight.shadow.camera.near = 8;
+    spotLight.shadow.camera.far = 200;
+    spotLight.shadow.mapSize.width = 256;
+    spotLight.shadow.mapSize.height = 256;
+    spotLight.shadow.bias = -0.002;
+    spotLight.shadow.radius = 4;
+    scene.add(spotLight);
+
+    dirLight = new THREE.DirectionalLight(0x8888ff, 3);
+    dirLight.position.set(3, 12, 17);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.near = 0.1;
+    dirLight.shadow.camera.far = 500;
+    dirLight.shadow.camera.right = 17;
+    dirLight.shadow.camera.left = -17;
+    dirLight.shadow.camera.top = 17;
+    dirLight.shadow.camera.bottom = -17;
+    dirLight.shadow.mapSize.width = 512;
+    dirLight.shadow.mapSize.height = 512;
+    dirLight.shadow.radius = 4;
+    dirLight.shadow.bias = -0.0005;
+
+    dirGroup = new THREE.Group();
+    dirGroup.add(dirLight);
+    scene.add(dirGroup);
+
+    // Geometry
+
+    const geometry = new THREE.TorusKnotGeometry(25, 8, 75, 20);
+    const material = new THREE.MeshPhongMaterial({
+        color: 0x999999,
+        shininess: 0,
+        specular: 0x222222,
+    });
+
+    torusKnot = new THREE.Mesh(geometry, material);
+    torusKnot.scale.multiplyScalar(1 / 18);
+    torusKnot.position.y = 3;
+    torusKnot.castShadow = true;
+    torusKnot.receiveShadow = true;
+    scene.add(torusKnot);
+
+    const cylinderGeometry = new THREE.CylinderGeometry(0.75, 0.75, 7, 32);
+
+    const pillar1 = new THREE.Mesh(cylinderGeometry, material);
+    pillar1.position.set(8, 3.5, 8);
+    pillar1.castShadow = true;
+    pillar1.receiveShadow = true;
+
+    const pillar2 = pillar1.clone();
+    pillar2.position.set(8, 3.5, -8);
+    const pillar3 = pillar1.clone();
+    pillar3.position.set(-8, 3.5, 8);
+    const pillar4 = pillar1.clone();
+    pillar4.position.set(-8, 3.5, -8);
+
+    scene.add(pillar1);
+    scene.add(pillar2);
+    scene.add(pillar3);
+    scene.add(pillar4);
+
+    const planeGeometry = new THREE.PlaneGeometry(200, 200);
+    const planeMaterial = new THREE.MeshPhongMaterial({
+        color: 0x999999,
+        shininess: 0,
+        specular: 0x111111,
+    });
+
+    const ground = new THREE.Mesh(planeGeometry, planeMaterial);
+    ground.rotation.x = -Math.PI / 2;
+    ground.scale.multiplyScalar(3);
+    ground.castShadow = true;
+    ground.receiveShadow = true;
+    scene.add(ground);
+}
+
+function initMisc() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.VSMShadowMap;
+
+    // Mouse control
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 2, 0);
+    controls.update();
+
+    clock = new THREE.Clock();
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate(time) {
+    const delta = clock.getDelta();
+
+    torusKnot.rotation.x += 0.25 * delta;
+    torusKnot.rotation.y += 0.5 * delta;
+    torusKnot.rotation.z += 1 * delta;
+
+    dirGroup.rotation.y += 0.7 * delta;
+    dirLight.position.z = 17 + Math.sin(time * 0.001) * 5;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_shadowmesh.ts b/examples-testing/examples/webgl_shadowmesh.ts
new file mode 100644
index 000000000..412fc0283
--- /dev/null
+++ b/examples-testing/examples/webgl_shadowmesh.ts
@@ -0,0 +1,250 @@
+import * as THREE from 'three';
+
+import { ShadowMesh } from 'three/addons/objects/ShadowMesh.js';
+
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+
+const scene = new THREE.Scene();
+const camera = new THREE.PerspectiveCamera(55, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 3000);
+const clock = new THREE.Clock();
+const renderer = new THREE.WebGLRenderer({ stencil: true });
+
+const sunLight = new THREE.DirectionalLight('rgb(255,255,255)', 3);
+let useDirectionalLight = true;
+let arrowHelper1, arrowHelper2, arrowHelper3;
+const arrowDirection = new THREE.Vector3();
+const arrowPosition1 = new THREE.Vector3();
+const arrowPosition2 = new THREE.Vector3();
+const arrowPosition3 = new THREE.Vector3();
+let groundMesh;
+let lightSphere, lightHolder;
+let pyramid, pyramidShadow;
+let sphere, sphereShadow;
+let cube, cubeShadow;
+let cylinder, cylinderShadow;
+let torus, torusShadow;
+const normalVector = new THREE.Vector3(0, 1, 0);
+const planeConstant = 0.01; // this value must be slightly higher than the groundMesh's y position of 0.0
+const groundPlane = new THREE.Plane(normalVector, planeConstant);
+const lightPosition4D = new THREE.Vector4();
+let verticalAngle = 0;
+let horizontalAngle = 0;
+let frameTime = 0;
+const TWO_PI = Math.PI * 2;
+
+init();
+
+function init() {
+    scene.background = new THREE.Color(0x0096ff);
+
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    document.getElementById('container').appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    camera.position.set(0, 2.5, 10);
+    scene.add(camera);
+    onWindowResize();
+
+    sunLight.position.set(5, 7, -1);
+    sunLight.lookAt(scene.position);
+    scene.add(sunLight);
+
+    lightPosition4D.x = sunLight.position.x;
+    lightPosition4D.y = sunLight.position.y;
+    lightPosition4D.z = sunLight.position.z;
+    // amount of light-ray divergence. Ranging from:
+    // 0.001 = sunlight(min divergence) to 1.0 = pointlight(max divergence)
+    lightPosition4D.w = 0.001; // must be slightly greater than 0, due to 0 causing matrixInverse errors
+
+    // YELLOW ARROW HELPERS
+    arrowDirection.subVectors(scene.position, sunLight.position).normalize();
+
+    arrowPosition1.copy(sunLight.position);
+    arrowHelper1 = new THREE.ArrowHelper(arrowDirection, arrowPosition1, 0.9, 0xffff00, 0.25, 0.08);
+    scene.add(arrowHelper1);
+
+    arrowPosition2.copy(sunLight.position).add(new THREE.Vector3(0, 0.2, 0));
+    arrowHelper2 = new THREE.ArrowHelper(arrowDirection, arrowPosition2, 0.9, 0xffff00, 0.25, 0.08);
+    scene.add(arrowHelper2);
+
+    arrowPosition3.copy(sunLight.position).add(new THREE.Vector3(0, -0.2, 0));
+    arrowHelper3 = new THREE.ArrowHelper(arrowDirection, arrowPosition3, 0.9, 0xffff00, 0.25, 0.08);
+    scene.add(arrowHelper3);
+
+    // LIGHTBULB
+    const lightSphereGeometry = new THREE.SphereGeometry(0.09);
+    const lightSphereMaterial = new THREE.MeshBasicMaterial({ color: 'rgb(255,255,255)' });
+    lightSphere = new THREE.Mesh(lightSphereGeometry, lightSphereMaterial);
+    scene.add(lightSphere);
+    lightSphere.visible = false;
+
+    const lightHolderGeometry = new THREE.CylinderGeometry(0.05, 0.05, 0.13);
+    const lightHolderMaterial = new THREE.MeshBasicMaterial({ color: 'rgb(75,75,75)' });
+    lightHolder = new THREE.Mesh(lightHolderGeometry, lightHolderMaterial);
+    scene.add(lightHolder);
+    lightHolder.visible = false;
+
+    // GROUND
+    const groundGeometry = new THREE.BoxGeometry(30, 0.01, 40);
+    const groundMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(0,130,0)' });
+    groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
+    groundMesh.position.y = 0.0; //this value must be slightly lower than the planeConstant (0.01) parameter above
+    scene.add(groundMesh);
+
+    // RED CUBE and CUBE's SHADOW
+    const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
+    const cubeMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(255,0,0)', emissive: 0x200000 });
+    cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
+    cube.position.z = -1;
+    scene.add(cube);
+
+    cubeShadow = new ShadowMesh(cube);
+    scene.add(cubeShadow);
+
+    // BLUE CYLINDER and CYLINDER's SHADOW
+    const cylinderGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2);
+    const cylinderMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(0,0,255)', emissive: 0x000020 });
+    cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
+    cylinder.position.z = -2.5;
+    scene.add(cylinder);
+
+    cylinderShadow = new ShadowMesh(cylinder);
+    scene.add(cylinderShadow);
+
+    // MAGENTA TORUS and TORUS' SHADOW
+    const torusGeometry = new THREE.TorusGeometry(1, 0.2, 10, 16, TWO_PI);
+    const torusMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(255,0,255)', emissive: 0x200020 });
+    torus = new THREE.Mesh(torusGeometry, torusMaterial);
+    torus.position.z = -6;
+    scene.add(torus);
+
+    torusShadow = new ShadowMesh(torus);
+    scene.add(torusShadow);
+
+    // WHITE SPHERE and SPHERE'S SHADOW
+    const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 10);
+    const sphereMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(255,255,255)', emissive: 0x222222 });
+    sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
+    sphere.position.set(4, 0.5, 2);
+    scene.add(sphere);
+
+    sphereShadow = new ShadowMesh(sphere);
+    scene.add(sphereShadow);
+
+    // YELLOW PYRAMID and PYRAMID'S SHADOW
+    const pyramidGeometry = new THREE.CylinderGeometry(0, 0.5, 2, 4);
+    const pyramidMaterial = new THREE.MeshPhongMaterial({
+        color: 'rgb(255,255,0)',
+        emissive: 0x440000,
+        flatShading: true,
+        shininess: 0,
+    });
+    pyramid = new THREE.Mesh(pyramidGeometry, pyramidMaterial);
+    pyramid.position.set(-4, 1, 2);
+    scene.add(pyramid);
+
+    pyramidShadow = new ShadowMesh(pyramid);
+    scene.add(pyramidShadow);
+
+    document.getElementById('lightButton').addEventListener('click', lightButtonHandler);
+}
+
+function animate() {
+    frameTime = clock.getDelta();
+
+    cube.rotation.x += 1.0 * frameTime;
+    cube.rotation.y += 1.0 * frameTime;
+
+    cylinder.rotation.y += 1.0 * frameTime;
+    cylinder.rotation.z -= 1.0 * frameTime;
+
+    torus.rotation.x -= 1.0 * frameTime;
+    torus.rotation.y -= 1.0 * frameTime;
+
+    pyramid.rotation.y += 0.5 * frameTime;
+
+    horizontalAngle += 0.5 * frameTime;
+    if (horizontalAngle > TWO_PI) horizontalAngle -= TWO_PI;
+    cube.position.x = Math.sin(horizontalAngle) * 4;
+    cylinder.position.x = Math.sin(horizontalAngle) * -4;
+    torus.position.x = Math.cos(horizontalAngle) * 4;
+
+    verticalAngle += 1.5 * frameTime;
+    if (verticalAngle > TWO_PI) verticalAngle -= TWO_PI;
+    cube.position.y = Math.sin(verticalAngle) * 2 + 2.9;
+    cylinder.position.y = Math.sin(verticalAngle) * 2 + 3.1;
+    torus.position.y = Math.cos(verticalAngle) * 2 + 3.3;
+
+    // update the ShadowMeshes to follow their shadow-casting objects
+    cubeShadow.update(groundPlane, lightPosition4D);
+    cylinderShadow.update(groundPlane, lightPosition4D);
+    torusShadow.update(groundPlane, lightPosition4D);
+    sphereShadow.update(groundPlane, lightPosition4D);
+    pyramidShadow.update(groundPlane, lightPosition4D);
+
+    renderer.render(scene, camera);
+}
+
+function onWindowResize() {
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
+    camera.updateProjectionMatrix();
+}
+
+function lightButtonHandler() {
+    useDirectionalLight = !useDirectionalLight;
+
+    if (useDirectionalLight) {
+        scene.background.setHex(0x0096ff);
+
+        groundMesh.material.color.setHex(0x008200);
+        sunLight.position.set(5, 7, -1);
+        sunLight.lookAt(scene.position);
+
+        lightPosition4D.x = sunLight.position.x;
+        lightPosition4D.y = sunLight.position.y;
+        lightPosition4D.z = sunLight.position.z;
+        lightPosition4D.w = 0.001; // more of a directional Light value
+
+        arrowHelper1.visible = true;
+        arrowHelper2.visible = true;
+        arrowHelper3.visible = true;
+
+        lightSphere.visible = false;
+        lightHolder.visible = false;
+
+        document.getElementById('lightButton').value = 'Switch to PointLight';
+    } else {
+        scene.background.setHex(0x000000);
+
+        groundMesh.material.color.setHex(0x969696);
+
+        sunLight.position.set(0, 6, -2);
+        sunLight.lookAt(scene.position);
+        lightSphere.position.copy(sunLight.position);
+        lightHolder.position.copy(lightSphere.position);
+        lightHolder.position.y += 0.12;
+
+        lightPosition4D.x = sunLight.position.x;
+        lightPosition4D.y = sunLight.position.y;
+        lightPosition4D.z = sunLight.position.z;
+        lightPosition4D.w = 0.9; // more of a point Light value
+
+        arrowHelper1.visible = false;
+        arrowHelper2.visible = false;
+        arrowHelper3.visible = false;
+
+        lightSphere.visible = true;
+        lightHolder.visible = true;
+
+        document.getElementById('lightButton').value = 'Switch to THREE.DirectionalLight';
+    }
+}
diff --git a/examples-testing/examples/webgl_simple_gi.ts b/examples-testing/examples/webgl_simple_gi.ts
new file mode 100644
index 000000000..4ab6dc895
--- /dev/null
+++ b/examples-testing/examples/webgl_simple_gi.ts
@@ -0,0 +1,172 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+class GIMesh extends THREE.Mesh {
+    copy(source) {
+        super.copy(source);
+
+        this.geometry = source.geometry.clone();
+
+        return this;
+    }
+}
+
+//
+
+const SimpleGI = function (renderer, scene) {
+    const SIZE = 32,
+        SIZE2 = SIZE * SIZE;
+
+    const camera = new THREE.PerspectiveCamera(90, 1, 0.01, 100);
+
+    scene.updateMatrixWorld(true);
+
+    let clone = scene.clone();
+    clone.matrixWorldAutoUpdate = false;
+
+    const rt = new THREE.WebGLRenderTarget(SIZE, SIZE);
+
+    const normalMatrix = new THREE.Matrix3();
+
+    const position = new THREE.Vector3();
+    const normal = new THREE.Vector3();
+
+    let bounces = 0;
+    let currentVertex = 0;
+
+    const color = new Float32Array(3);
+    const buffer = new Uint8Array(SIZE2 * 4);
+
+    function compute() {
+        if (bounces === 3) return;
+
+        const object = scene.children[0]; // torusKnot
+        const geometry = object.geometry;
+
+        const attributes = geometry.attributes;
+        const positions = attributes.position.array;
+        const normals = attributes.normal.array;
+
+        if (attributes.color === undefined) {
+            const colors = new Float32Array(positions.length);
+            geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage));
+        }
+
+        const colors = attributes.color.array;
+
+        const startVertex = currentVertex;
+        const totalVertex = positions.length / 3;
+
+        for (let i = 0; i < 32; i++) {
+            if (currentVertex >= totalVertex) break;
+
+            position.fromArray(positions, currentVertex * 3);
+            position.applyMatrix4(object.matrixWorld);
+
+            normal.fromArray(normals, currentVertex * 3);
+            normal.applyMatrix3(normalMatrix.getNormalMatrix(object.matrixWorld)).normalize();
+
+            camera.position.copy(position);
+            camera.lookAt(position.add(normal));
+
+            renderer.setRenderTarget(rt);
+            renderer.render(clone, camera);
+
+            renderer.readRenderTargetPixels(rt, 0, 0, SIZE, SIZE, buffer);
+
+            color[0] = 0;
+            color[1] = 0;
+            color[2] = 0;
+
+            for (let k = 0, kl = buffer.length; k < kl; k += 4) {
+                color[0] += buffer[k + 0];
+                color[1] += buffer[k + 1];
+                color[2] += buffer[k + 2];
+            }
+
+            colors[currentVertex * 3 + 0] = color[0] / (SIZE2 * 255);
+            colors[currentVertex * 3 + 1] = color[1] / (SIZE2 * 255);
+            colors[currentVertex * 3 + 2] = color[2] / (SIZE2 * 255);
+
+            currentVertex++;
+        }
+
+        attributes.color.addUpdateRange(startVertex * 3, (currentVertex - startVertex) * 3);
+        attributes.color.needsUpdate = true;
+
+        if (currentVertex >= totalVertex) {
+            clone = scene.clone();
+            clone.matrixWorldAutoUpdate = false;
+
+            bounces++;
+            currentVertex = 0;
+        }
+
+        requestAnimationFrame(compute);
+    }
+
+    requestAnimationFrame(compute);
+};
+
+//
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 4;
+
+    scene = new THREE.Scene();
+
+    // torus knot
+
+    const torusGeometry = new THREE.TorusKnotGeometry(0.75, 0.3, 128, 32, 1);
+    const material = new THREE.MeshBasicMaterial({ vertexColors: true });
+
+    const torusKnot = new GIMesh(torusGeometry, material);
+    scene.add(torusKnot);
+
+    // room
+
+    const materials = [];
+
+    for (let i = 0; i < 8; i++) {
+        materials.push(new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, side: THREE.BackSide }));
+    }
+
+    const boxGeometry = new THREE.BoxGeometry(3, 3, 3);
+
+    const box = new THREE.Mesh(boxGeometry, materials);
+    scene.add(box);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    new SimpleGI(renderer, scene);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.setRenderTarget(null);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_sprites.ts b/examples-testing/examples/webgl_sprites.ts
new file mode 100644
index 000000000..2e4189347
--- /dev/null
+++ b/examples-testing/examples/webgl_sprites.ts
@@ -0,0 +1,187 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+let cameraOrtho, sceneOrtho;
+
+let spriteTL, spriteTR, spriteBL, spriteBR, spriteC;
+
+let mapC;
+
+let group;
+
+init();
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera = new THREE.PerspectiveCamera(60, width / height, 1, 2100);
+    camera.position.z = 1500;
+
+    cameraOrtho = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 1, 10);
+    cameraOrtho.position.z = 10;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1500, 2100);
+
+    sceneOrtho = new THREE.Scene();
+
+    // create sprites
+
+    const amount = 200;
+    const radius = 500;
+
+    const textureLoader = new THREE.TextureLoader();
+
+    textureLoader.load('textures/sprite0.png', createHUDSprites);
+    const mapB = textureLoader.load('textures/sprite1.png');
+    mapC = textureLoader.load('textures/sprite2.png');
+
+    mapB.colorSpace = THREE.SRGBColorSpace;
+    mapC.colorSpace = THREE.SRGBColorSpace;
+
+    group = new THREE.Group();
+
+    const materialC = new THREE.SpriteMaterial({ map: mapC, color: 0xffffff, fog: true });
+    const materialB = new THREE.SpriteMaterial({ map: mapB, color: 0xffffff, fog: true });
+
+    for (let a = 0; a < amount; a++) {
+        const x = Math.random() - 0.5;
+        const y = Math.random() - 0.5;
+        const z = Math.random() - 0.5;
+
+        let material;
+
+        if (z < 0) {
+            material = materialB.clone();
+        } else {
+            material = materialC.clone();
+            material.color.setHSL(0.5 * Math.random(), 0.75, 0.5);
+            material.map.offset.set(-0.5, -0.5);
+            material.map.repeat.set(2, 2);
+        }
+
+        const sprite = new THREE.Sprite(material);
+
+        sprite.position.set(x, y, z);
+        sprite.position.normalize();
+        sprite.position.multiplyScalar(radius);
+
+        group.add(sprite);
+    }
+
+    scene.add(group);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false; // To allow render overlay on top of sprited sphere
+
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createHUDSprites(texture) {
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const material = new THREE.SpriteMaterial({ map: texture });
+
+    const width = material.map.image.width;
+    const height = material.map.image.height;
+
+    spriteTL = new THREE.Sprite(material);
+    spriteTL.center.set(0.0, 1.0);
+    spriteTL.scale.set(width, height, 1);
+    sceneOrtho.add(spriteTL);
+
+    spriteTR = new THREE.Sprite(material);
+    spriteTR.center.set(1.0, 1.0);
+    spriteTR.scale.set(width, height, 1);
+    sceneOrtho.add(spriteTR);
+
+    spriteBL = new THREE.Sprite(material);
+    spriteBL.center.set(0.0, 0.0);
+    spriteBL.scale.set(width, height, 1);
+    sceneOrtho.add(spriteBL);
+
+    spriteBR = new THREE.Sprite(material);
+    spriteBR.center.set(1.0, 0.0);
+    spriteBR.scale.set(width, height, 1);
+    sceneOrtho.add(spriteBR);
+
+    spriteC = new THREE.Sprite(material);
+    spriteC.center.set(0.5, 0.5);
+    spriteC.scale.set(width, height, 1);
+    sceneOrtho.add(spriteC);
+
+    updateHUDSprites();
+}
+
+function updateHUDSprites() {
+    const width = window.innerWidth / 2;
+    const height = window.innerHeight / 2;
+
+    spriteTL.position.set(-width, height, 1); // top left
+    spriteTR.position.set(width, height, 1); // top right
+    spriteBL.position.set(-width, -height, 1); // bottom left
+    spriteBR.position.set(width, -height, 1); // bottom right
+    spriteC.position.set(0, 0, 1); // center
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    cameraOrtho.left = -width / 2;
+    cameraOrtho.right = width / 2;
+    cameraOrtho.top = height / 2;
+    cameraOrtho.bottom = -height / 2;
+    cameraOrtho.updateProjectionMatrix();
+
+    updateHUDSprites();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = Date.now() / 1000;
+
+    for (let i = 0, l = group.children.length; i < l; i++) {
+        const sprite = group.children[i];
+        const material = sprite.material;
+        const scale = Math.sin(time + sprite.position.x * 0.01) * 0.3 + 1.0;
+
+        let imageWidth = 1;
+        let imageHeight = 1;
+
+        if (material.map && material.map.image && material.map.image.width) {
+            imageWidth = material.map.image.width;
+            imageHeight = material.map.image.height;
+        }
+
+        sprite.material.rotation += 0.1 * (i / l);
+        sprite.scale.set(scale * imageWidth, scale * imageHeight, 1.0);
+
+        if (material.map !== mapC) {
+            material.opacity = Math.sin(time + sprite.position.x * 0.01) * 0.4 + 0.6;
+        }
+    }
+
+    group.rotation.x = time * 0.5;
+    group.rotation.y = time * 0.75;
+    group.rotation.z = time * 1.0;
+
+    renderer.clear();
+    renderer.render(scene, camera);
+    renderer.clearDepth();
+    renderer.render(sceneOrtho, cameraOrtho);
+}
diff --git a/examples-testing/examples/webgl_test_memory.ts b/examples-testing/examples/webgl_test_memory.ts
new file mode 100644
index 000000000..f5d0e112d
--- /dev/null
+++ b/examples-testing/examples/webgl_test_memory.ts
@@ -0,0 +1,65 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 200;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+}
+
+function createImage() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 256;
+    canvas.height = 256;
+
+    const context = canvas.getContext('2d');
+    context.fillStyle =
+        'rgb(' +
+        Math.floor(Math.random() * 256) +
+        ',' +
+        Math.floor(Math.random() * 256) +
+        ',' +
+        Math.floor(Math.random() * 256) +
+        ')';
+    context.fillRect(0, 0, 256, 256);
+
+    return canvas;
+}
+
+//
+
+function animate() {
+    const geometry = new THREE.SphereGeometry(50, Math.random() * 64, Math.random() * 32);
+
+    const texture = new THREE.CanvasTexture(createImage());
+
+    const material = new THREE.MeshBasicMaterial({ map: texture, wireframe: true });
+
+    const mesh = new THREE.Mesh(geometry, material);
+
+    scene.add(mesh);
+
+    renderer.render(scene, camera);
+
+    scene.remove(mesh);
+
+    // clean up
+
+    geometry.dispose();
+    material.dispose();
+    texture.dispose();
+}
diff --git a/examples-testing/examples/webgl_test_memory2.ts b/examples-testing/examples/webgl_test_memory2.ts
new file mode 100644
index 000000000..366a27914
--- /dev/null
+++ b/examples-testing/examples/webgl_test_memory2.ts
@@ -0,0 +1,81 @@
+import * as THREE from 'three';
+
+const N = 100;
+
+let container;
+
+let camera, scene, renderer;
+
+let geometry;
+
+const meshes = [];
+
+let fragmentShader, vertexShader;
+
+init();
+setInterval(render, 1000 / 60);
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    vertexShader = document.getElementById('vertexShader').textContent;
+    fragmentShader = document.getElementById('fragmentShader').textContent;
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 2000;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    geometry = new THREE.SphereGeometry(15, 64, 32);
+
+    for (let i = 0; i < N; i++) {
+        const material = new THREE.ShaderMaterial({
+            vertexShader: vertexShader,
+            fragmentShader: generateFragmentShader(),
+        });
+
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = (0.5 - Math.random()) * 1000;
+        mesh.position.y = (0.5 - Math.random()) * 1000;
+        mesh.position.z = (0.5 - Math.random()) * 1000;
+
+        scene.add(mesh);
+
+        meshes.push(mesh);
+    }
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    container.appendChild(renderer.domElement);
+}
+
+//
+
+function generateFragmentShader() {
+    return fragmentShader.replace('XXX', Math.random() + ',' + Math.random() + ',' + Math.random());
+}
+
+function render() {
+    for (let i = 0; i < N; i++) {
+        const mesh = meshes[i];
+        mesh.material = new THREE.ShaderMaterial({
+            vertexShader: vertexShader,
+            fragmentShader: generateFragmentShader(),
+        });
+    }
+
+    renderer.render(scene, camera);
+
+    console.log('before', renderer.info.programs.length);
+
+    for (let i = 0; i < N; i++) {
+        const mesh = meshes[i];
+        mesh.material.dispose();
+    }
+
+    console.log('after', renderer.info.programs.length);
+}
diff --git a/examples-testing/examples/webgl_test_wide_gamut.ts b/examples-testing/examples/webgl_test_wide_gamut.ts
new file mode 100644
index 000000000..693dd4593
--- /dev/null
+++ b/examples-testing/examples/webgl_test_wide_gamut.ts
@@ -0,0 +1,118 @@
+import * as THREE from 'three';
+
+import WebGL from 'three/addons/capabilities/WebGL.js';
+
+let container, camera, renderer, loader;
+let sceneL, sceneR, textureL, textureR;
+
+let sliderPos = window.innerWidth / 2;
+
+const slider = document.querySelector('.slider');
+
+const isP3Context = WebGL.isColorSpaceAvailable(THREE.DisplayP3ColorSpace);
+
+if (isP3Context) {
+    THREE.ColorManagement.workingColorSpace = THREE.LinearDisplayP3ColorSpace;
+}
+
+init();
+
+function init() {
+    container = document.querySelector('.container');
+
+    sceneL = new THREE.Scene();
+    sceneR = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 6;
+
+    loader = new THREE.TextureLoader();
+
+    initTextures();
+    initSlider();
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.setScissorTest(true);
+    container.appendChild(renderer.domElement);
+
+    if (isP3Context && window.matchMedia('( color-gamut: p3 )').matches) {
+        renderer.outputColorSpace = THREE.DisplayP3ColorSpace;
+    }
+
+    window.addEventListener('resize', onWindowResize);
+    window.matchMedia('( color-gamut: p3 )').addEventListener('change', onGamutChange);
+}
+
+async function initTextures() {
+    const path = 'textures/wide_gamut/logo_{colorSpace}.png';
+
+    textureL = await loader.loadAsync(path.replace('{colorSpace}', 'srgb'));
+    textureR = await loader.loadAsync(path.replace('{colorSpace}', 'p3'));
+
+    textureL.colorSpace = THREE.SRGBColorSpace;
+    textureR.colorSpace = THREE.DisplayP3ColorSpace;
+
+    sceneL.background = THREE.TextureUtils.contain(textureL, window.innerWidth / window.innerHeight);
+    sceneR.background = THREE.TextureUtils.contain(textureR, window.innerWidth / window.innerHeight);
+}
+
+function initSlider() {
+    function onPointerDown() {
+        if (event.isPrimary === false) return;
+
+        window.addEventListener('pointermove', onPointerMove);
+        window.addEventListener('pointerup', onPointerUp);
+    }
+
+    function onPointerUp() {
+        window.removeEventListener('pointermove', onPointerMove);
+        window.removeEventListener('pointerup', onPointerUp);
+    }
+
+    function onPointerMove(e) {
+        if (event.isPrimary === false) return;
+
+        updateSlider(e.pageX);
+    }
+
+    updateSlider(sliderPos);
+
+    slider.style.touchAction = 'none'; // disable touch scroll
+    slider.addEventListener('pointerdown', onPointerDown);
+}
+
+function updateSlider(offset) {
+    sliderPos = Math.max(10, Math.min(window.innerWidth - 10, offset));
+
+    slider.style.left = sliderPos - slider.offsetWidth / 2 + 'px';
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    THREE.TextureUtils.contain(sceneL.background, window.innerWidth / window.innerHeight);
+    THREE.TextureUtils.contain(sceneR.background, window.innerWidth / window.innerHeight);
+
+    updateSlider(sliderPos);
+}
+
+function onGamutChange({ matches }) {
+    renderer.outputColorSpace = isP3Context && matches ? THREE.DisplayP3ColorSpace : THREE.SRGBColorSpace;
+
+    textureL.needsUpdate = true;
+    textureR.needsUpdate = true;
+}
+
+function animate() {
+    renderer.setScissor(0, 0, sliderPos, window.innerHeight);
+    renderer.render(sceneL, camera);
+
+    renderer.setScissor(sliderPos, 0, window.innerWidth, window.innerHeight);
+    renderer.render(sceneR, camera);
+}
diff --git a/examples-testing/examples/webgl_texture2darray_compressed.ts b/examples-testing/examples/webgl_texture2darray_compressed.ts
new file mode 100644
index 000000000..f263be706
--- /dev/null
+++ b/examples-testing/examples/webgl_texture2darray_compressed.ts
@@ -0,0 +1,88 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+
+let camera, scene, mesh, renderer, stats, clock;
+
+const planeWidth = 50;
+const planeHeight = 25;
+
+let depthStep = 1;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
+    camera.position.z = 70;
+
+    scene = new THREE.Scene();
+
+    //
+    clock = new THREE.Clock();
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const ktx2Loader = new KTX2Loader();
+    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
+    ktx2Loader.detectSupport(renderer);
+
+    ktx2Loader.load('textures/spiritedaway.ktx2', function (texturearray) {
+        const material = new THREE.ShaderMaterial({
+            uniforms: {
+                diffuse: { value: texturearray },
+                depth: { value: 55 },
+                size: { value: new THREE.Vector2(planeWidth, planeHeight) },
+            },
+            vertexShader: document.getElementById('vs').textContent.trim(),
+            fragmentShader: document.getElementById('fs').textContent.trim(),
+            glslVersion: THREE.GLSL3,
+        });
+
+        const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight);
+
+        mesh = new THREE.Mesh(geometry, material);
+
+        scene.add(mesh);
+    });
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    if (mesh) {
+        const delta = clock.getDelta() * 10;
+
+        depthStep += delta;
+
+        const value = depthStep % 5;
+
+        mesh.material.uniforms['depth'].value = value;
+    }
+
+    render();
+    stats.update();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_texture2darray_layerupdate.ts b/examples-testing/examples/webgl_texture2darray_layerupdate.ts
new file mode 100644
index 000000000..0cc136cb7
--- /dev/null
+++ b/examples-testing/examples/webgl_texture2darray_layerupdate.ts
@@ -0,0 +1,131 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+
+let camera, scene, mesh, renderer;
+
+const planeWidth = 20;
+const planeHeight = 10;
+
+init();
+
+async function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
+    camera.position.z = 70;
+
+    scene = new THREE.Scene();
+
+    // Configure the renderer.
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    container.appendChild(renderer.domElement);
+
+    // Configure the KTX2 loader.
+
+    const ktx2Loader = new KTX2Loader();
+    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
+    ktx2Loader.detectSupport(renderer);
+
+    // Load several KTX2 textures which will later be used to modify
+    // specific texture array layers.
+
+    const spiritedaway = await ktx2Loader.loadAsync('textures/spiritedaway.ktx2');
+
+    // Create a texture array for rendering.
+
+    const layerByteLength = THREE.TextureUtils.getByteLength(
+        spiritedaway.image.width,
+        spiritedaway.image.height,
+        spiritedaway.format,
+        spiritedaway.type,
+    );
+
+    const textureArray = new THREE.CompressedArrayTexture(
+        [
+            {
+                data: new Uint8Array(layerByteLength * 3),
+                width: spiritedaway.image.width,
+                height: spiritedaway.image.height,
+            },
+        ],
+        spiritedaway.image.width,
+        spiritedaway.image.height,
+        3,
+        spiritedaway.format,
+        spiritedaway.type,
+    );
+
+    // Setup the GUI
+
+    const formData = {
+        srcLayer: 0,
+        destLayer: 0,
+        transfer() {
+            const layerElementLength = layerByteLength / spiritedaway.mipmaps[0].data.BYTES_PER_ELEMENT;
+            textureArray.mipmaps[0].data.set(
+                spiritedaway.mipmaps[0].data.subarray(
+                    layerElementLength * (formData.srcLayer % spiritedaway.image.depth),
+                    layerElementLength * ((formData.srcLayer % spiritedaway.image.depth) + 1),
+                ),
+                layerByteLength * formData.destLayer,
+            );
+            textureArray.addLayerUpdate(formData.destLayer);
+            textureArray.needsUpdate = true;
+
+            renderer.render(scene, camera);
+        },
+    };
+
+    const gui = new GUI();
+    gui.add(formData, 'srcLayer', 0, spiritedaway.image.depth - 1, 1);
+    gui.add(formData, 'destLayer', 0, textureArray.image.depth - 1, 1);
+    gui.add(formData, 'transfer');
+
+    /// Setup the scene.
+
+    const material = new THREE.ShaderMaterial({
+        uniforms: {
+            diffuse: { value: textureArray },
+            size: { value: new THREE.Vector2(planeWidth, planeHeight) },
+        },
+        vertexShader: document.getElementById('vs').textContent.trim(),
+        fragmentShader: document.getElementById('fs').textContent.trim(),
+        glslVersion: THREE.GLSL3,
+    });
+
+    const geometry = new THREE.InstancedBufferGeometry();
+    geometry.copy(new THREE.PlaneGeometry(planeWidth, planeHeight));
+    geometry.instanceCount = 3;
+
+    const instancedIndexAttribute = new THREE.InstancedBufferAttribute(new Uint16Array([0, 1, 2]), 1, false, 1);
+    instancedIndexAttribute.gpuType = THREE.IntType;
+    geometry.setAttribute('instancedIndex', instancedIndexAttribute);
+
+    mesh = new THREE.InstancedMesh(geometry, material, 3);
+
+    scene.add(mesh);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // Initialize the texture array by first rendering the spirited away
+    // frames in order.
+
+    textureArray.mipmaps[0].data.set(spiritedaway.mipmaps[0].data.subarray(0, textureArray.mipmaps[0].data.length));
+    textureArray.needsUpdate = true;
+    renderer.render(scene, camera);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_texture3d.ts b/examples-testing/examples/webgl_texture3d.ts
new file mode 100644
index 000000000..977dbadb7
--- /dev/null
+++ b/examples-testing/examples/webgl_texture3d.ts
@@ -0,0 +1,128 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { NRRDLoader } from 'three/addons/loaders/NRRDLoader.js';
+import { VolumeRenderShader1 } from 'three/addons/shaders/VolumeShader.js';
+
+let renderer, scene, camera, controls, material, volconfig, cmtextures;
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    // Create renderer
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    // Create camera (The volume renderer does not work very well with perspective yet)
+    const h = 512; // frustum height
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera((-h * aspect) / 2, (h * aspect) / 2, h / 2, -h / 2, 1, 1000);
+    camera.position.set(-64, -64, 128);
+    camera.up.set(0, 0, 1); // In our data, z is up
+
+    // Create controls
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render);
+    controls.target.set(64, 64, 128);
+    controls.minZoom = 0.5;
+    controls.maxZoom = 4;
+    controls.enablePan = false;
+    controls.update();
+
+    // scene.add( new AxesHelper( 128 ) );
+
+    // Lighting is baked into the shader a.t.m.
+    // let dirLight = new DirectionalLight( 0xffffff );
+
+    // The gui for interaction
+    volconfig = { clim1: 0, clim2: 1, renderstyle: 'iso', isothreshold: 0.15, colormap: 'viridis' };
+    const gui = new GUI();
+    gui.add(volconfig, 'clim1', 0, 1, 0.01).onChange(updateUniforms);
+    gui.add(volconfig, 'clim2', 0, 1, 0.01).onChange(updateUniforms);
+    gui.add(volconfig, 'colormap', { gray: 'gray', viridis: 'viridis' }).onChange(updateUniforms);
+    gui.add(volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' }).onChange(updateUniforms);
+    gui.add(volconfig, 'isothreshold', 0, 1, 0.01).onChange(updateUniforms);
+
+    // Load the data ...
+    new NRRDLoader().load('models/nrrd/stent.nrrd', function (volume) {
+        // Texture to hold the volume. We have scalars, so we put our data in the red channel.
+        // THREEJS will select R32F (33326) based on the THREE.RedFormat and THREE.FloatType.
+        // Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
+        // TODO: look the dtype up in the volume metadata
+        const texture = new THREE.Data3DTexture(volume.data, volume.xLength, volume.yLength, volume.zLength);
+        texture.format = THREE.RedFormat;
+        texture.type = THREE.FloatType;
+        texture.minFilter = texture.magFilter = THREE.LinearFilter;
+        texture.unpackAlignment = 1;
+        texture.needsUpdate = true;
+
+        // Colormap textures
+        cmtextures = {
+            viridis: new THREE.TextureLoader().load('textures/cm_viridis.png', render),
+            gray: new THREE.TextureLoader().load('textures/cm_gray.png', render),
+        };
+
+        // Material
+        const shader = VolumeRenderShader1;
+
+        const uniforms = THREE.UniformsUtils.clone(shader.uniforms);
+
+        uniforms['u_data'].value = texture;
+        uniforms['u_size'].value.set(volume.xLength, volume.yLength, volume.zLength);
+        uniforms['u_clim'].value.set(volconfig.clim1, volconfig.clim2);
+        uniforms['u_renderstyle'].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
+        uniforms['u_renderthreshold'].value = volconfig.isothreshold; // For ISO renderstyle
+        uniforms['u_cmdata'].value = cmtextures[volconfig.colormap];
+
+        material = new THREE.ShaderMaterial({
+            uniforms: uniforms,
+            vertexShader: shader.vertexShader,
+            fragmentShader: shader.fragmentShader,
+            side: THREE.BackSide, // The volume shader uses the backface as its "reference point"
+        });
+
+        // THREE.Mesh
+        const geometry = new THREE.BoxGeometry(volume.xLength, volume.yLength, volume.zLength);
+        geometry.translate(volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5);
+
+        const mesh = new THREE.Mesh(geometry, material);
+        scene.add(mesh);
+
+        render();
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function updateUniforms() {
+    material.uniforms['u_clim'].value.set(volconfig.clim1, volconfig.clim2);
+    material.uniforms['u_renderstyle'].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
+    material.uniforms['u_renderthreshold'].value = volconfig.isothreshold; // For ISO renderstyle
+    material.uniforms['u_cmdata'].value = cmtextures[volconfig.colormap];
+
+    render();
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    const aspect = window.innerWidth / window.innerHeight;
+
+    const frustumHeight = camera.top - camera.bottom;
+
+    camera.left = (-frustumHeight * aspect) / 2;
+    camera.right = (frustumHeight * aspect) / 2;
+
+    camera.updateProjectionMatrix();
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_texture3d_partialupdate.ts b/examples-testing/examples/webgl_texture3d_partialupdate.ts
new file mode 100644
index 000000000..1ad6d2646
--- /dev/null
+++ b/examples-testing/examples/webgl_texture3d_partialupdate.ts
@@ -0,0 +1,326 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const INITIAL_CLOUD_SIZE = 128;
+
+let renderer, scene, camera;
+let mesh;
+let prevTime = performance.now();
+let cloudTexture = null;
+
+init();
+
+function generateCloudTexture(size, scaleFactor = 1.0) {
+    const data = new Uint8Array(size * size * size);
+    const scale = (scaleFactor * 10.0) / size;
+
+    let i = 0;
+    const perlin = new ImprovedNoise();
+    const vector = new THREE.Vector3();
+
+    for (let z = 0; z < size; z++) {
+        for (let y = 0; y < size; y++) {
+            for (let x = 0; x < size; x++) {
+                const dist = vector
+                    .set(x, y, z)
+                    .subScalar(size / 2)
+                    .divideScalar(size)
+                    .length();
+                const fadingFactor = (1.0 - dist) * (1.0 - dist);
+                data[i] = (128 + 128 * perlin.noise((x * scale) / 1.5, y * scale, (z * scale) / 1.5)) * fadingFactor;
+
+                i++;
+            }
+        }
+    }
+
+    return new THREE.Data3DTexture(data, size, size, size);
+}
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 1.5);
+
+    new OrbitControls(camera, renderer.domElement);
+
+    // Sky
+
+    const canvas = document.createElement('canvas');
+    canvas.width = 1;
+    canvas.height = 32;
+
+    const context = canvas.getContext('2d');
+    const gradient = context.createLinearGradient(0, 0, 0, 32);
+    gradient.addColorStop(0.0, '#014a84');
+    gradient.addColorStop(0.5, '#0561a0');
+    gradient.addColorStop(1.0, '#437ab6');
+    context.fillStyle = gradient;
+    context.fillRect(0, 0, 1, 32);
+
+    const skyMap = new THREE.CanvasTexture(canvas);
+    skyMap.colorSpace = THREE.SRGBColorSpace;
+
+    const sky = new THREE.Mesh(
+        new THREE.SphereGeometry(10),
+        new THREE.MeshBasicMaterial({ map: skyMap, side: THREE.BackSide }),
+    );
+    scene.add(sky);
+
+    // Texture
+
+    const texture = new THREE.Data3DTexture(
+        new Uint8Array(INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE).fill(0),
+        INITIAL_CLOUD_SIZE,
+        INITIAL_CLOUD_SIZE,
+        INITIAL_CLOUD_SIZE,
+    );
+    texture.format = THREE.RedFormat;
+    texture.minFilter = THREE.LinearFilter;
+    texture.magFilter = THREE.LinearFilter;
+    texture.unpackAlignment = 1;
+    texture.needsUpdate = true;
+
+    cloudTexture = texture;
+
+    // Material
+
+    const vertexShader = /* glsl */ `
+					in vec3 position;
+
+					uniform mat4 modelMatrix;
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+					uniform vec3 cameraPos;
+
+					out vec3 vOrigin;
+					out vec3 vDirection;
+
+					void main() {
+						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+
+						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
+						vDirection = position - vOrigin;
+
+						gl_Position = projectionMatrix * mvPosition;
+					}
+				`;
+
+    const fragmentShader = /* glsl */ `
+					precision highp float;
+					precision highp sampler3D;
+
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+
+					in vec3 vOrigin;
+					in vec3 vDirection;
+
+					out vec4 color;
+
+					uniform vec3 base;
+					uniform sampler3D map;
+
+					uniform float threshold;
+					uniform float range;
+					uniform float opacity;
+					uniform float steps;
+					uniform float frame;
+
+					uint wang_hash(uint seed)
+					{
+							seed = (seed ^ 61u) ^ (seed >> 16u);
+							seed *= 9u;
+							seed = seed ^ (seed >> 4u);
+							seed *= 0x27d4eb2du;
+							seed = seed ^ (seed >> 15u);
+							return seed;
+					}
+
+					float randomFloat(inout uint seed)
+					{
+							return float(wang_hash(seed)) / 4294967296.;
+					}
+
+					vec2 hitBox( vec3 orig, vec3 dir ) {
+						const vec3 box_min = vec3( - 0.5 );
+						const vec3 box_max = vec3( 0.5 );
+						vec3 inv_dir = 1.0 / dir;
+						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
+						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
+						vec3 tmin = min( tmin_tmp, tmax_tmp );
+						vec3 tmax = max( tmin_tmp, tmax_tmp );
+						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
+						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
+						return vec2( t0, t1 );
+					}
+
+					float sample1( vec3 p ) {
+						return texture( map, p ).r;
+					}
+
+					float shading( vec3 coord ) {
+						float step = 0.01;
+						return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
+					}
+
+					vec4 linearToSRGB( in vec4 value ) {
+						return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
+					}
+
+					void main(){
+						vec3 rayDir = normalize( vDirection );
+						vec2 bounds = hitBox( vOrigin, rayDir );
+
+						if ( bounds.x > bounds.y ) discard;
+
+						bounds.x = max( bounds.x, 0.0 );
+
+						vec3 p = vOrigin + bounds.x * rayDir;
+						vec3 inc = 1.0 / abs( rayDir );
+						float delta = min( inc.x, min( inc.y, inc.z ) );
+						delta /= steps;
+
+						// Jitter
+
+						// Nice little seed from
+						// https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
+						uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
+						vec3 size = vec3( textureSize( map, 0 ) );
+						float randNum = randomFloat( seed ) * 2.0 - 1.0;
+						p += rayDir * randNum * ( 1.0 / size );
+
+						//
+
+						vec4 ac = vec4( base, 0.0 );
+
+						for ( float t = bounds.x; t < bounds.y; t += delta ) {
+
+							float d = sample1( p + 0.5 );
+
+							d = smoothstep( threshold - range, threshold + range, d ) * opacity;
+
+							float col = shading( p + 0.5 ) * 3.0 + ( ( p.x + p.y ) * 0.25 ) + 0.2;
+
+							ac.rgb += ( 1.0 - ac.a ) * d * col;
+
+							ac.a += ( 1.0 - ac.a ) * d;
+
+							if ( ac.a >= 0.95 ) break;
+
+							p += rayDir * delta;
+
+						}
+
+						color = linearToSRGB( ac );
+
+						if ( color.a == 0.0 ) discard;
+
+					}
+				`;
+
+    const geometry = new THREE.BoxGeometry(1, 1, 1);
+    const material = new THREE.RawShaderMaterial({
+        glslVersion: THREE.GLSL3,
+        uniforms: {
+            base: { value: new THREE.Color(0x798aa0) },
+            map: { value: texture },
+            cameraPos: { value: new THREE.Vector3() },
+            threshold: { value: 0.25 },
+            opacity: { value: 0.25 },
+            range: { value: 0.1 },
+            steps: { value: 100 },
+            frame: { value: 0 },
+        },
+        vertexShader,
+        fragmentShader,
+        side: THREE.BackSide,
+        transparent: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const parameters = {
+        threshold: 0.25,
+        opacity: 0.25,
+        range: 0.1,
+        steps: 100,
+    };
+
+    function update() {
+        material.uniforms.threshold.value = parameters.threshold;
+        material.uniforms.opacity.value = parameters.opacity;
+        material.uniforms.range.value = parameters.range;
+        material.uniforms.steps.value = parameters.steps;
+    }
+
+    const gui = new GUI();
+    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'opacity', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'range', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'steps', 0, 200, 1).onChange(update);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+let curr = 0;
+const countPerRow = 4;
+const countPerSlice = countPerRow * countPerRow;
+const sliceCount = 4;
+const totalCount = sliceCount * countPerSlice;
+const margins = 8;
+
+const perElementPaddedSize = (INITIAL_CLOUD_SIZE - margins) / countPerRow;
+const perElementSize = Math.floor((INITIAL_CLOUD_SIZE - 1) / countPerRow);
+
+function animate() {
+    const time = performance.now();
+    if (time - prevTime > 1500.0 && curr < totalCount) {
+        const position = new THREE.Vector3(
+            Math.floor(curr % countPerRow) * perElementSize + margins * 0.5,
+            Math.floor((curr % countPerSlice) / countPerRow) * perElementSize + margins * 0.5,
+            Math.floor(curr / countPerSlice) * perElementSize + margins * 0.5,
+        ).floor();
+
+        const maxDimension = perElementPaddedSize - 1;
+        const box = new THREE.Box3(
+            new THREE.Vector3(0, 0, 0),
+            new THREE.Vector3(maxDimension, maxDimension, maxDimension),
+        );
+        const scaleFactor = (Math.random() + 0.5) * 0.5;
+        const source = generateCloudTexture(perElementPaddedSize, scaleFactor);
+
+        renderer.copyTextureToTexture3D(source, cloudTexture, box, position);
+
+        prevTime = time;
+
+        curr++;
+    }
+
+    mesh.material.uniforms.cameraPos.value.copy(camera.position);
+    // mesh.rotation.y = - performance.now() / 7500;
+
+    mesh.material.uniforms.frame.value++;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_tonemapping.ts b/examples-testing/examples/webgl_tonemapping.ts
new file mode 100644
index 000000000..08115cf3e
--- /dev/null
+++ b/examples-testing/examples/webgl_tonemapping.ts
@@ -0,0 +1,163 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let mesh, renderer, scene, camera, controls;
+let gui,
+    guiExposure = null;
+
+const params = {
+    exposure: 1.0,
+    toneMapping: 'AgX',
+    blurriness: 0.3,
+    intensity: 1.0,
+};
+
+const toneMappingOptions = {
+    None: THREE.NoToneMapping,
+    Linear: THREE.LinearToneMapping,
+    Reinhard: THREE.ReinhardToneMapping,
+    Cineon: THREE.CineonToneMapping,
+    ACESFilmic: THREE.ACESFilmicToneMapping,
+    AgX: THREE.AgXToneMapping,
+    Neutral: THREE.NeutralToneMapping,
+    Custom: THREE.CustomToneMapping,
+};
+
+init().catch(function (err) {
+    console.error(err);
+});
+
+async function init() {
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = toneMappingOptions[params.toneMapping];
+    renderer.toneMappingExposure = params.exposure;
+
+    // Set CustomToneMapping to Uncharted2
+    // source: http://filmicworlds.com/blog/filmic-tonemapping-operators/
+
+    THREE.ShaderChunk.tonemapping_pars_fragment = THREE.ShaderChunk.tonemapping_pars_fragment.replace(
+        'vec3 CustomToneMapping( vec3 color ) { return color; }',
+
+        `#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
+
+					float toneMappingWhitePoint = 1.0;
+
+					vec3 CustomToneMapping( vec3 color ) {
+						color *= toneMappingExposure;
+						return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );
+
+					}`,
+    );
+
+    scene = new THREE.Scene();
+    scene.backgroundBlurriness = params.blurriness;
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.enableZoom = false;
+    controls.enablePan = false;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+
+    const [texture, gltf] = await Promise.all([
+        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
+        gltfLoader.loadAsync('DamagedHelmet.gltf'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.environment = texture;
+
+    // model
+
+    mesh = gltf.scene.getObjectByName('node_damagedHelmet_-6514');
+    scene.add(mesh);
+
+    render();
+
+    window.addEventListener('resize', onWindowResize);
+
+    gui = new GUI();
+    const toneMappingFolder = gui.addFolder('tone mapping');
+
+    toneMappingFolder
+        .add(params, 'toneMapping', Object.keys(toneMappingOptions))
+
+        .onChange(function () {
+            updateGUI(toneMappingFolder);
+
+            renderer.toneMapping = toneMappingOptions[params.toneMapping];
+            render();
+        });
+
+    const backgroundFolder = gui.addFolder('background');
+
+    backgroundFolder
+        .add(params, 'blurriness', 0, 1)
+
+        .onChange(function (value) {
+            scene.backgroundBlurriness = value;
+            render();
+        });
+
+    backgroundFolder
+        .add(params, 'intensity', 0, 1)
+
+        .onChange(function (value) {
+            scene.backgroundIntensity = value;
+            render();
+        });
+
+    updateGUI(toneMappingFolder);
+
+    gui.open();
+}
+
+function updateGUI(folder) {
+    if (guiExposure !== null) {
+        guiExposure.destroy();
+        guiExposure = null;
+    }
+
+    if (params.toneMapping !== 'None') {
+        guiExposure = folder
+            .add(params, 'exposure', 0, 2)
+
+            .onChange(function () {
+                renderer.toneMappingExposure = params.exposure;
+                render();
+            });
+    }
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_ubo.ts b/examples-testing/examples/webgl_ubo.ts
new file mode 100644
index 000000000..01064f115
--- /dev/null
+++ b/examples-testing/examples/webgl_ubo.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, clock;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 25);
+
+    scene = new THREE.Scene();
+    camera.lookAt(scene.position);
+
+    clock = new THREE.Clock();
+
+    // geometry
+
+    const geometry1 = new THREE.TetrahedronGeometry();
+    const geometry2 = new THREE.BoxGeometry();
+
+    // texture
+
+    const texture = new THREE.TextureLoader().load('textures/crate.gif');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    // uniforms groups
+
+    // Camera and lighting related data are perfect examples of using UBOs since you have to store these
+    // data just once. They can be shared across all shader programs.
+
+    const cameraUniformsGroup = new THREE.UniformsGroup();
+    cameraUniformsGroup.setName('ViewData');
+    cameraUniformsGroup.add(new THREE.Uniform(camera.projectionMatrix)); // projection matrix
+    cameraUniformsGroup.add(new THREE.Uniform(camera.matrixWorldInverse)); // view matrix
+
+    const lightingUniformsGroup = new THREE.UniformsGroup();
+    lightingUniformsGroup.setName('LightingData');
+    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Vector3(0, 0, 10))); // light position
+    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0x7c7c7c))); // ambient color
+    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0xd5d5d5))); // diffuse color
+    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0xe7e7e7))); // specular color
+    lightingUniformsGroup.add(new THREE.Uniform(64)); // shininess
+
+    // materials
+
+    const material1 = new THREE.RawShaderMaterial({
+        uniforms: {
+            modelMatrix: { value: null },
+            normalMatrix: { value: null },
+            color: { value: null },
+        },
+        vertexShader: document.getElementById('vertexShader1').textContent,
+        fragmentShader: document.getElementById('fragmentShader1').textContent,
+        glslVersion: THREE.GLSL3,
+    });
+
+    const material2 = new THREE.RawShaderMaterial({
+        uniforms: {
+            modelMatrix: { value: null },
+            diffuseMap: { value: null },
+        },
+        vertexShader: document.getElementById('vertexShader2').textContent,
+        fragmentShader: document.getElementById('fragmentShader2').textContent,
+        glslVersion: THREE.GLSL3,
+    });
+
+    // meshes
+
+    for (let i = 0; i < 200; i++) {
+        let mesh;
+
+        if (i % 2 === 0) {
+            mesh = new THREE.Mesh(geometry1, material1.clone());
+
+            mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
+            mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
+            mesh.material.uniforms.normalMatrix.value = mesh.normalMatrix;
+            mesh.material.uniforms.color.value = new THREE.Color(0xffffff * Math.random());
+        } else {
+            mesh = new THREE.Mesh(geometry2, material2.clone());
+
+            mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
+            mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
+            mesh.material.uniforms.diffuseMap.value = texture;
+        }
+
+        scene.add(mesh);
+
+        const s = 1 + Math.random() * 0.5;
+
+        mesh.scale.x = s;
+        mesh.scale.y = s;
+        mesh.scale.z = s;
+
+        mesh.rotation.x = Math.random() * Math.PI;
+        mesh.rotation.y = Math.random() * Math.PI;
+        mesh.rotation.z = Math.random() * Math.PI;
+
+        mesh.position.x = Math.random() * 40 - 20;
+        mesh.position.y = Math.random() * 40 - 20;
+        mesh.position.z = Math.random() * 20 - 10;
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize, false);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const delta = clock.getDelta();
+
+    scene.traverse(function (child) {
+        if (child.isMesh) {
+            child.rotation.x += delta * 0.5;
+            child.rotation.y += delta * 0.3;
+        }
+    });
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_ubo_arrays.ts b/examples-testing/examples/webgl_ubo_arrays.ts
new file mode 100644
index 000000000..d846e1443
--- /dev/null
+++ b/examples-testing/examples/webgl_ubo_arrays.ts
@@ -0,0 +1,171 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, clock, stats;
+
+let lightingUniformsGroup, lightCenters;
+
+const container = document.getElementById('container');
+
+const pointLightsMax = 300;
+
+const api = {
+    count: 200,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 50, 50);
+
+    scene = new THREE.Scene();
+    camera.lookAt(scene.position);
+
+    clock = new THREE.Clock();
+
+    // geometry
+
+    const geometry = new THREE.SphereGeometry();
+
+    // uniforms groups
+
+    lightingUniformsGroup = new THREE.UniformsGroup();
+    lightingUniformsGroup.setName('LightingData');
+
+    const data = [];
+    const dataColors = [];
+    lightCenters = [];
+
+    for (let i = 0; i < pointLightsMax; i++) {
+        const col = new THREE.Color(0xffffff * Math.random()).toArray();
+        const x = Math.random() * 50 - 25;
+        const z = Math.random() * 50 - 25;
+
+        data.push(new THREE.Uniform(new THREE.Vector4(x, 1, z, 0))); // light position
+        dataColors.push(new THREE.Uniform(new THREE.Vector4(col[0], col[1], col[2], 0))); // light color
+
+        // Store the center positions
+        lightCenters.push({ x, z });
+    }
+
+    lightingUniformsGroup.add(data); // light position
+    lightingUniformsGroup.add(dataColors); // light position
+    lightingUniformsGroup.add(new THREE.Uniform(pointLightsMax)); // light position
+
+    const cameraUniformsGroup = new THREE.UniformsGroup();
+    cameraUniformsGroup.setName('ViewData');
+    cameraUniformsGroup.add(new THREE.Uniform(camera.projectionMatrix)); // projection matrix
+    cameraUniformsGroup.add(new THREE.Uniform(camera.matrixWorldInverse)); // view matrix
+
+    const material = new THREE.RawShaderMaterial({
+        uniforms: {
+            modelMatrix: { value: null },
+            normalMatrix: { value: null },
+        },
+        // uniformsGroups: [ cameraUniformsGroup, lightingUniformsGroup ],
+        name: 'Box',
+        defines: {
+            POINTLIGHTS_MAX: pointLightsMax,
+        },
+        vertexShader: document.getElementById('vertexShader').textContent,
+        fragmentShader: document.getElementById('fragmentShader').textContent,
+        glslVersion: THREE.GLSL3,
+    });
+
+    const plane = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), material.clone());
+    plane.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
+    plane.material.uniforms.modelMatrix.value = plane.matrixWorld;
+    plane.material.uniforms.normalMatrix.value = plane.normalMatrix;
+    plane.rotation.x = -Math.PI / 2;
+    plane.position.y = -1;
+    scene.add(plane);
+
+    // meshes
+    const gridSize = { x: 10, y: 1, z: 10 };
+    const spacing = 6;
+
+    for (let i = 0; i < gridSize.x; i++) {
+        for (let j = 0; j < gridSize.y; j++) {
+            for (let k = 0; k < gridSize.z; k++) {
+                const mesh = new THREE.Mesh(geometry, material.clone());
+                mesh.name = 'Sphere';
+                mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
+                mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
+                mesh.material.uniforms.normalMatrix.value = mesh.normalMatrix;
+                scene.add(mesh);
+
+                mesh.position.x = i * spacing - (gridSize.x * spacing) / 2;
+                mesh.position.y = 0;
+                mesh.position.z = k * spacing - (gridSize.z * spacing) / 2;
+            }
+        }
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize, false);
+
+    // controls
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enablePan = false;
+
+    // stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // gui
+    const gui = new GUI();
+    gui.add(api, 'count', 1, pointLightsMax)
+        .step(1)
+        .onChange(function () {
+            lightingUniformsGroup.uniforms[2].value = api.count;
+        });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const elapsedTime = clock.getElapsedTime();
+
+    const lights = lightingUniformsGroup.uniforms[0];
+
+    // Parameters for circular movement
+    const radius = 5; // Smaller radius for individual circular movements
+    const speed = 0.5; // Speed of rotation
+
+    // Update each light's position
+    for (let i = 0; i < lights.length; i++) {
+        const light = lights[i];
+        const center = lightCenters[i];
+
+        // Calculate circular movement around the light's center
+        const angle = speed * elapsedTime + i * 0.5; // Phase difference for each light
+        const x = center.x + Math.sin(angle) * radius;
+        const z = center.z + Math.cos(angle) * radius;
+
+        // Update the light's position
+        light.value.set(x, 1, z, 0);
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgl_video_kinect.ts b/examples-testing/examples/webgl_video_kinect.ts
new file mode 100644
index 000000000..4f0e2f113
--- /dev/null
+++ b/examples-testing/examples/webgl_video_kinect.ts
@@ -0,0 +1,113 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, camera, renderer;
+let geometry, mesh, material;
+let mouse, center;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    const info = document.createElement('div');
+    info.id = 'info';
+    info.innerHTML = '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - kinect';
+    document.body.appendChild(info);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.set(0, 0, 500);
+
+    scene = new THREE.Scene();
+    center = new THREE.Vector3();
+    center.z = -1000;
+
+    const video = document.getElementById('video');
+
+    const texture = new THREE.VideoTexture(video);
+    texture.minFilter = THREE.NearestFilter;
+
+    const width = 640,
+        height = 480;
+    const nearClipping = 850,
+        farClipping = 4000;
+
+    geometry = new THREE.BufferGeometry();
+
+    const vertices = new Float32Array(width * height * 3);
+
+    for (let i = 0, j = 0, l = vertices.length; i < l; i += 3, j++) {
+        vertices[i] = j % width;
+        vertices[i + 1] = Math.floor(j / width);
+    }
+
+    geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
+
+    material = new THREE.ShaderMaterial({
+        uniforms: {
+            map: { value: texture },
+            width: { value: width },
+            height: { value: height },
+            nearClipping: { value: nearClipping },
+            farClipping: { value: farClipping },
+
+            pointSize: { value: 2 },
+            zOffset: { value: 1000 },
+        },
+        vertexShader: document.getElementById('vs').textContent,
+        fragmentShader: document.getElementById('fs').textContent,
+        blending: THREE.AdditiveBlending,
+        depthTest: false,
+        depthWrite: false,
+        transparent: true,
+    });
+
+    mesh = new THREE.Points(geometry, material);
+    scene.add(mesh);
+
+    const gui = new GUI();
+    gui.add(material.uniforms.nearClipping, 'value', 1, 10000, 1.0).name('nearClipping');
+    gui.add(material.uniforms.farClipping, 'value', 1, 10000, 1.0).name('farClipping');
+    gui.add(material.uniforms.pointSize, 'value', 1, 10, 1.0).name('pointSize');
+    gui.add(material.uniforms.zOffset, 'value', 0, 4000, 1.0).name('zOffset');
+
+    video.play();
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    mouse = new THREE.Vector3(0, 0, 1);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouse.x = (event.clientX - window.innerWidth / 2) * 8;
+    mouse.y = (event.clientY - window.innerHeight / 2) * 8;
+}
+
+function animate() {
+    camera.position.x += (mouse.x - camera.position.x) * 0.05;
+    camera.position.y += (-mouse.y - camera.position.y) * 0.05;
+    camera.lookAt(center);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_video_panorama_equirectangular.ts b/examples-testing/examples/webgl_video_panorama_equirectangular.ts
new file mode 100644
index 000000000..866eca16a
--- /dev/null
+++ b/examples-testing/examples/webgl_video_panorama_equirectangular.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+
+let isUserInteracting = false,
+    lon = 0,
+    lat = 0,
+    phi = 0,
+    theta = 0,
+    onPointerDownPointerX = 0,
+    onPointerDownPointerY = 0,
+    onPointerDownLon = 0,
+    onPointerDownLat = 0;
+
+const distance = 0.5;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.25, 10);
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.SphereGeometry(5, 60, 40);
+    // invert the geometry on the x-axis so that all of the faces point inward
+    geometry.scale(-1, 1, 1);
+
+    const video = document.getElementById('video');
+    video.play();
+
+    const texture = new THREE.VideoTexture(video);
+    texture.colorSpace = THREE.SRGBColorSpace;
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    document.addEventListener('pointerdown', onPointerDown);
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerDown(event) {
+    isUserInteracting = true;
+
+    onPointerDownPointerX = event.clientX;
+    onPointerDownPointerY = event.clientY;
+
+    onPointerDownLon = lon;
+    onPointerDownLat = lat;
+}
+
+function onPointerMove(event) {
+    if (isUserInteracting === true) {
+        lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;
+        lat = (onPointerDownPointerY - event.clientY) * 0.1 + onPointerDownLat;
+    }
+}
+
+function onPointerUp() {
+    isUserInteracting = false;
+}
+
+function animate() {
+    lat = Math.max(-85, Math.min(85, lat));
+    phi = THREE.MathUtils.degToRad(90 - lat);
+    theta = THREE.MathUtils.degToRad(lon);
+
+    camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
+    camera.position.y = distance * Math.cos(phi);
+    camera.position.z = distance * Math.sin(phi) * Math.sin(theta);
+
+    camera.lookAt(0, 0, 0);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_volume_cloud.ts b/examples-testing/examples/webgl_volume_cloud.ts
new file mode 100644
index 000000000..77dd8de43
--- /dev/null
+++ b/examples-testing/examples/webgl_volume_cloud.ts
@@ -0,0 +1,279 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let renderer, scene, camera;
+let mesh;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 1.5);
+
+    new OrbitControls(camera, renderer.domElement);
+
+    // Sky
+
+    const canvas = document.createElement('canvas');
+    canvas.width = 1;
+    canvas.height = 32;
+
+    const context = canvas.getContext('2d');
+    const gradient = context.createLinearGradient(0, 0, 0, 32);
+    gradient.addColorStop(0.0, '#014a84');
+    gradient.addColorStop(0.5, '#0561a0');
+    gradient.addColorStop(1.0, '#437ab6');
+    context.fillStyle = gradient;
+    context.fillRect(0, 0, 1, 32);
+
+    const skyMap = new THREE.CanvasTexture(canvas);
+    skyMap.colorSpace = THREE.SRGBColorSpace;
+
+    const sky = new THREE.Mesh(
+        new THREE.SphereGeometry(10),
+        new THREE.MeshBasicMaterial({ map: skyMap, side: THREE.BackSide }),
+    );
+    scene.add(sky);
+
+    // Texture
+
+    const size = 128;
+    const data = new Uint8Array(size * size * size);
+
+    let i = 0;
+    const scale = 0.05;
+    const perlin = new ImprovedNoise();
+    const vector = new THREE.Vector3();
+
+    for (let z = 0; z < size; z++) {
+        for (let y = 0; y < size; y++) {
+            for (let x = 0; x < size; x++) {
+                const d =
+                    1.0 -
+                    vector
+                        .set(x, y, z)
+                        .subScalar(size / 2)
+                        .divideScalar(size)
+                        .length();
+                data[i] = (128 + 128 * perlin.noise((x * scale) / 1.5, y * scale, (z * scale) / 1.5)) * d * d;
+                i++;
+            }
+        }
+    }
+
+    const texture = new THREE.Data3DTexture(data, size, size, size);
+    texture.format = THREE.RedFormat;
+    texture.minFilter = THREE.LinearFilter;
+    texture.magFilter = THREE.LinearFilter;
+    texture.unpackAlignment = 1;
+    texture.needsUpdate = true;
+
+    // Material
+
+    const vertexShader = /* glsl */ `
+					in vec3 position;
+
+					uniform mat4 modelMatrix;
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+					uniform vec3 cameraPos;
+
+					out vec3 vOrigin;
+					out vec3 vDirection;
+
+					void main() {
+						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+
+						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
+						vDirection = position - vOrigin;
+
+						gl_Position = projectionMatrix * mvPosition;
+					}
+				`;
+
+    const fragmentShader = /* glsl */ `
+					precision highp float;
+					precision highp sampler3D;
+
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+
+					in vec3 vOrigin;
+					in vec3 vDirection;
+
+					out vec4 color;
+
+					uniform vec3 base;
+					uniform sampler3D map;
+
+					uniform float threshold;
+					uniform float range;
+					uniform float opacity;
+					uniform float steps;
+					uniform float frame;
+
+					uint wang_hash(uint seed)
+					{
+							seed = (seed ^ 61u) ^ (seed >> 16u);
+							seed *= 9u;
+							seed = seed ^ (seed >> 4u);
+							seed *= 0x27d4eb2du;
+							seed = seed ^ (seed >> 15u);
+							return seed;
+					}
+
+					float randomFloat(inout uint seed)
+					{
+							return float(wang_hash(seed)) / 4294967296.;
+					}
+
+					vec2 hitBox( vec3 orig, vec3 dir ) {
+						const vec3 box_min = vec3( - 0.5 );
+						const vec3 box_max = vec3( 0.5 );
+						vec3 inv_dir = 1.0 / dir;
+						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
+						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
+						vec3 tmin = min( tmin_tmp, tmax_tmp );
+						vec3 tmax = max( tmin_tmp, tmax_tmp );
+						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
+						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
+						return vec2( t0, t1 );
+					}
+
+					float sample1( vec3 p ) {
+						return texture( map, p ).r;
+					}
+
+					float shading( vec3 coord ) {
+						float step = 0.01;
+						return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
+					}
+
+					vec4 linearToSRGB( in vec4 value ) {
+						return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
+					}
+
+					void main(){
+						vec3 rayDir = normalize( vDirection );
+						vec2 bounds = hitBox( vOrigin, rayDir );
+
+						if ( bounds.x > bounds.y ) discard;
+
+						bounds.x = max( bounds.x, 0.0 );
+
+						vec3 p = vOrigin + bounds.x * rayDir;
+						vec3 inc = 1.0 / abs( rayDir );
+						float delta = min( inc.x, min( inc.y, inc.z ) );
+						delta /= steps;
+
+						// Jitter
+
+						// Nice little seed from
+						// https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
+						uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
+						vec3 size = vec3( textureSize( map, 0 ) );
+						float randNum = randomFloat( seed ) * 2.0 - 1.0;
+						p += rayDir * randNum * ( 1.0 / size );
+
+						//
+
+						vec4 ac = vec4( base, 0.0 );
+
+						for ( float t = bounds.x; t < bounds.y; t += delta ) {
+
+							float d = sample1( p + 0.5 );
+
+							d = smoothstep( threshold - range, threshold + range, d ) * opacity;
+
+							float col = shading( p + 0.5 ) * 3.0 + ( ( p.x + p.y ) * 0.25 ) + 0.2;
+
+							ac.rgb += ( 1.0 - ac.a ) * d * col;
+
+							ac.a += ( 1.0 - ac.a ) * d;
+
+							if ( ac.a >= 0.95 ) break;
+
+							p += rayDir * delta;
+
+						}
+
+						color = linearToSRGB( ac );
+
+						if ( color.a == 0.0 ) discard;
+
+					}
+				`;
+
+    const geometry = new THREE.BoxGeometry(1, 1, 1);
+    const material = new THREE.RawShaderMaterial({
+        glslVersion: THREE.GLSL3,
+        uniforms: {
+            base: { value: new THREE.Color(0x798aa0) },
+            map: { value: texture },
+            cameraPos: { value: new THREE.Vector3() },
+            threshold: { value: 0.25 },
+            opacity: { value: 0.25 },
+            range: { value: 0.1 },
+            steps: { value: 100 },
+            frame: { value: 0 },
+        },
+        vertexShader,
+        fragmentShader,
+        side: THREE.BackSide,
+        transparent: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const parameters = {
+        threshold: 0.25,
+        opacity: 0.25,
+        range: 0.1,
+        steps: 100,
+    };
+
+    function update() {
+        material.uniforms.threshold.value = parameters.threshold;
+        material.uniforms.opacity.value = parameters.opacity;
+        material.uniforms.range.value = parameters.range;
+        material.uniforms.steps.value = parameters.steps;
+    }
+
+    const gui = new GUI();
+    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'opacity', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'range', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'steps', 0, 200, 1).onChange(update);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.material.uniforms.cameraPos.value.copy(camera.position);
+    mesh.rotation.y = -performance.now() / 7500;
+
+    mesh.material.uniforms.frame.value++;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_volume_instancing.ts b/examples-testing/examples/webgl_volume_instancing.ts
new file mode 100644
index 000000000..bf90eeea9
--- /dev/null
+++ b/examples-testing/examples/webgl_volume_instancing.ts
@@ -0,0 +1,192 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { VOXLoader, VOXData3DTexture } from 'three/addons/loaders/VOXLoader.js';
+
+let renderer, scene, camera, controls, clock;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
+    camera.position.set(0, 0, 4);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -1.0;
+    controls.enableDamping = true;
+
+    clock = new THREE.Clock();
+
+    // Material
+
+    const vertexShader = /* glsl */ `
+					in vec3 position;
+					in mat4 instanceMatrix;
+
+					uniform mat4 modelMatrix;
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+					uniform vec3 cameraPos;
+
+					out vec3 vOrigin;
+					out vec3 vDirection;
+
+					void main() {
+						vec4 mvPosition = modelViewMatrix * instanceMatrix * vec4( position, 1.0 );
+
+						vOrigin = vec3( inverse( instanceMatrix * modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
+						vDirection = position - vOrigin;
+
+						gl_Position = projectionMatrix * mvPosition;
+					}
+				`;
+
+    const fragmentShader = /* glsl */ `
+					precision highp float;
+					precision highp sampler3D;
+
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+
+					in vec3 vOrigin;
+					in vec3 vDirection;
+
+					out vec4 color;
+
+					uniform sampler3D map;
+
+					uniform float threshold;
+					uniform float steps;
+
+					vec2 hitBox( vec3 orig, vec3 dir ) {
+						const vec3 box_min = vec3( - 0.5 );
+						const vec3 box_max = vec3( 0.5 );
+						vec3 inv_dir = 1.0 / dir;
+						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
+						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
+						vec3 tmin = min( tmin_tmp, tmax_tmp );
+						vec3 tmax = max( tmin_tmp, tmax_tmp );
+						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
+						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
+						return vec2( t0, t1 );
+					}
+
+					float sample1( vec3 p ) {
+						return texture( map, p ).r;
+					}
+
+					#define epsilon .0001
+
+					vec3 normal( vec3 coord ) {
+						if ( coord.x < epsilon ) return vec3( 1.0, 0.0, 0.0 );
+						if ( coord.y < epsilon ) return vec3( 0.0, 1.0, 0.0 );
+						if ( coord.z < epsilon ) return vec3( 0.0, 0.0, 1.0 );
+						if ( coord.x > 1.0 - epsilon ) return vec3( - 1.0, 0.0, 0.0 );
+						if ( coord.y > 1.0 - epsilon ) return vec3( 0.0, - 1.0, 0.0 );
+						if ( coord.z > 1.0 - epsilon ) return vec3( 0.0, 0.0, - 1.0 );
+
+						float step = 0.01;
+						float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
+						float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
+						float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );
+
+						return normalize( vec3( x, y, z ) );
+					}
+
+					void main(){
+
+						vec3 rayDir = normalize( vDirection );
+						vec2 bounds = hitBox( vOrigin, rayDir );
+
+						if ( bounds.x > bounds.y ) discard;
+
+						bounds.x = max( bounds.x, 0.0 );
+
+						vec3 p = vOrigin + bounds.x * rayDir;
+						vec3 inc = 1.0 / abs( rayDir );
+						float delta = min( inc.x, min( inc.y, inc.z ) );
+						delta /= 50.0;
+
+						for ( float t = bounds.x; t < bounds.y; t += delta ) {
+
+							float d = sample1( p + 0.5 );
+
+							if ( d > 0.5 ) {
+
+								color.rgb = p * 2.0; // normal( p + 0.5 ); // * 0.5 + ( p * 1.5 + 0.25 );
+								color.a = 1.;
+								break;
+
+							}
+
+							p += rayDir * delta;
+
+						}
+
+						if ( color.a == 0.0 ) discard;
+
+					}
+				`;
+
+    const loader = new VOXLoader();
+    loader.load('models/vox/menger.vox', function (chunks) {
+        for (let i = 0; i < chunks.length; i++) {
+            const chunk = chunks[i];
+
+            const geometry = new THREE.BoxGeometry(1, 1, 1);
+            const material = new THREE.RawShaderMaterial({
+                glslVersion: THREE.GLSL3,
+                uniforms: {
+                    map: { value: new VOXData3DTexture(chunk) },
+                    cameraPos: { value: new THREE.Vector3() },
+                },
+                vertexShader,
+                fragmentShader,
+                side: THREE.BackSide,
+            });
+
+            const mesh = new THREE.InstancedMesh(geometry, material, 50000);
+            mesh.onBeforeRender = function () {
+                this.material.uniforms.cameraPos.value.copy(camera.position);
+            };
+
+            const transform = new THREE.Object3D();
+
+            for (let i = 0; i < mesh.count; i++) {
+                transform.position.random().subScalar(0.5).multiplyScalar(150);
+                transform.rotation.x = Math.random() * Math.PI;
+                transform.rotation.y = Math.random() * Math.PI;
+                transform.rotation.z = Math.random() * Math.PI;
+                transform.updateMatrix();
+
+                mesh.setMatrixAt(i, transform.matrix);
+            }
+
+            scene.add(mesh);
+        }
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+    controls.update(delta);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_volume_perlin.ts b/examples-testing/examples/webgl_volume_perlin.ts
new file mode 100644
index 000000000..a98f9a682
--- /dev/null
+++ b/examples-testing/examples/webgl_volume_perlin.ts
@@ -0,0 +1,208 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let renderer, scene, camera;
+let mesh;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 2);
+
+    new OrbitControls(camera, renderer.domElement);
+
+    // Texture
+
+    const size = 128;
+    const data = new Uint8Array(size * size * size);
+
+    let i = 0;
+    const perlin = new ImprovedNoise();
+    const vector = new THREE.Vector3();
+
+    for (let z = 0; z < size; z++) {
+        for (let y = 0; y < size; y++) {
+            for (let x = 0; x < size; x++) {
+                vector.set(x, y, z).divideScalar(size);
+
+                const d = perlin.noise(vector.x * 6.5, vector.y * 6.5, vector.z * 6.5);
+
+                data[i++] = d * 128 + 128;
+            }
+        }
+    }
+
+    const texture = new THREE.Data3DTexture(data, size, size, size);
+    texture.format = THREE.RedFormat;
+    texture.minFilter = THREE.LinearFilter;
+    texture.magFilter = THREE.LinearFilter;
+    texture.unpackAlignment = 1;
+    texture.needsUpdate = true;
+
+    // Material
+
+    const vertexShader = /* glsl */ `
+					in vec3 position;
+
+					uniform mat4 modelMatrix;
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+					uniform vec3 cameraPos;
+
+					out vec3 vOrigin;
+					out vec3 vDirection;
+
+					void main() {
+						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+
+						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
+						vDirection = position - vOrigin;
+
+						gl_Position = projectionMatrix * mvPosition;
+					}
+				`;
+
+    const fragmentShader = /* glsl */ `
+					precision highp float;
+					precision highp sampler3D;
+
+					uniform mat4 modelViewMatrix;
+					uniform mat4 projectionMatrix;
+
+					in vec3 vOrigin;
+					in vec3 vDirection;
+
+					out vec4 color;
+
+					uniform sampler3D map;
+
+					uniform float threshold;
+					uniform float steps;
+
+					vec2 hitBox( vec3 orig, vec3 dir ) {
+						const vec3 box_min = vec3( - 0.5 );
+						const vec3 box_max = vec3( 0.5 );
+						vec3 inv_dir = 1.0 / dir;
+						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
+						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
+						vec3 tmin = min( tmin_tmp, tmax_tmp );
+						vec3 tmax = max( tmin_tmp, tmax_tmp );
+						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
+						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
+						return vec2( t0, t1 );
+					}
+
+					float sample1( vec3 p ) {
+						return texture( map, p ).r;
+					}
+
+					#define epsilon .0001
+
+					vec3 normal( vec3 coord ) {
+						if ( coord.x < epsilon ) return vec3( 1.0, 0.0, 0.0 );
+						if ( coord.y < epsilon ) return vec3( 0.0, 1.0, 0.0 );
+						if ( coord.z < epsilon ) return vec3( 0.0, 0.0, 1.0 );
+						if ( coord.x > 1.0 - epsilon ) return vec3( - 1.0, 0.0, 0.0 );
+						if ( coord.y > 1.0 - epsilon ) return vec3( 0.0, - 1.0, 0.0 );
+						if ( coord.z > 1.0 - epsilon ) return vec3( 0.0, 0.0, - 1.0 );
+
+						float step = 0.01;
+						float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
+						float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
+						float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );
+
+						return normalize( vec3( x, y, z ) );
+					}
+
+					void main(){
+
+						vec3 rayDir = normalize( vDirection );
+						vec2 bounds = hitBox( vOrigin, rayDir );
+
+						if ( bounds.x > bounds.y ) discard;
+
+						bounds.x = max( bounds.x, 0.0 );
+
+						vec3 p = vOrigin + bounds.x * rayDir;
+						vec3 inc = 1.0 / abs( rayDir );
+						float delta = min( inc.x, min( inc.y, inc.z ) );
+						delta /= steps;
+
+						for ( float t = bounds.x; t < bounds.y; t += delta ) {
+
+							float d = sample1( p + 0.5 );
+
+							if ( d > threshold ) {
+
+								color.rgb = normal( p + 0.5 ) * 0.5 + ( p * 1.5 + 0.25 );
+								color.a = 1.;
+								break;
+
+							}
+
+							p += rayDir * delta;
+
+						}
+
+						if ( color.a == 0.0 ) discard;
+
+					}
+				`;
+
+    const geometry = new THREE.BoxGeometry(1, 1, 1);
+    const material = new THREE.RawShaderMaterial({
+        glslVersion: THREE.GLSL3,
+        uniforms: {
+            map: { value: texture },
+            cameraPos: { value: new THREE.Vector3() },
+            threshold: { value: 0.6 },
+            steps: { value: 200 },
+        },
+        vertexShader,
+        fragmentShader,
+        side: THREE.BackSide,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const parameters = { threshold: 0.6, steps: 200 };
+
+    function update() {
+        material.uniforms.threshold.value = parameters.threshold;
+        material.uniforms.steps.value = parameters.steps;
+    }
+
+    const gui = new GUI();
+    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
+    gui.add(parameters, 'steps', 0, 300, 1).onChange(update);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    mesh.material.uniforms.cameraPos.value.copy(camera.position);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_water.ts b/examples-testing/examples/webgl_water.ts
new file mode 100644
index 000000000..496a5f855
--- /dev/null
+++ b/examples-testing/examples/webgl_water.ts
@@ -0,0 +1,162 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Water } from 'three/addons/objects/Water2.js';
+
+let scene, camera, clock, renderer, water;
+
+let torusKnot;
+
+const params = {
+    color: '#ffffff',
+    scale: 4,
+    flowX: 1,
+    flowY: 1,
+};
+
+init();
+
+function init() {
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
+    camera.position.set(-15, 7, 15);
+    camera.lookAt(scene.position);
+
+    // clock
+
+    clock = new THREE.Clock();
+
+    // mesh
+
+    const torusKnotGeometry = new THREE.TorusKnotGeometry(3, 1, 256, 32);
+    const torusKnotMaterial = new THREE.MeshNormalMaterial();
+
+    torusKnot = new THREE.Mesh(torusKnotGeometry, torusKnotMaterial);
+    torusKnot.position.y = 4;
+    torusKnot.scale.set(0.5, 0.5, 0.5);
+    scene.add(torusKnot);
+
+    // ground
+
+    const groundGeometry = new THREE.PlaneGeometry(20, 20);
+    const groundMaterial = new THREE.MeshStandardMaterial({ roughness: 0.8, metalness: 0.4 });
+    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
+    ground.rotation.x = Math.PI * -0.5;
+    scene.add(ground);
+
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 16;
+        map.repeat.set(4, 4);
+        map.colorSpace = THREE.SRGBColorSpace;
+        groundMaterial.map = map;
+        groundMaterial.needsUpdate = true;
+    });
+
+    // water
+
+    const waterGeometry = new THREE.PlaneGeometry(20, 20);
+
+    water = new Water(waterGeometry, {
+        color: params.color,
+        scale: params.scale,
+        flowDirection: new THREE.Vector2(params.flowX, params.flowY),
+        textureWidth: 1024,
+        textureHeight: 1024,
+    });
+
+    water.position.y = 1;
+    water.rotation.x = Math.PI * -0.5;
+    scene.add(water);
+
+    // skybox
+
+    const cubeTextureLoader = new THREE.CubeTextureLoader();
+    cubeTextureLoader.setPath('textures/cube/Park2/');
+
+    const cubeTexture = cubeTextureLoader.load([
+        'posx.jpg',
+        'negx.jpg',
+        'posy.jpg',
+        'negy.jpg',
+        'posz.jpg',
+        'negz.jpg',
+    ]);
+
+    scene.background = cubeTexture;
+
+    // light
+
+    const ambientLight = new THREE.AmbientLight(0xe7e7e7, 1.2);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
+    directionalLight.position.set(-1, 1, 1);
+    scene.add(directionalLight);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // gui
+
+    const gui = new GUI();
+
+    gui.addColor(params, 'color').onChange(function (value) {
+        water.material.uniforms['color'].value.set(value);
+    });
+    gui.add(params, 'scale', 1, 10).onChange(function (value) {
+        water.material.uniforms['config'].value.w = value;
+    });
+    gui.add(params, 'flowX', -1, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            water.material.uniforms['flowDirection'].value.x = value;
+            water.material.uniforms['flowDirection'].value.normalize();
+        });
+    gui.add(params, 'flowY', -1, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            water.material.uniforms['flowDirection'].value.y = value;
+            water.material.uniforms['flowDirection'].value.normalize();
+        });
+
+    gui.open();
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 5;
+    controls.maxDistance = 50;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    torusKnot.rotation.x += delta;
+    torusKnot.rotation.y += delta * 0.5;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgl_water_flowmap.ts b/examples-testing/examples/webgl_water_flowmap.ts
new file mode 100644
index 000000000..d0255e431
--- /dev/null
+++ b/examples-testing/examples/webgl_water_flowmap.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Water } from 'three/addons/objects/Water2.js';
+
+let scene, camera, renderer, water;
+
+init();
+
+function init() {
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
+    camera.position.set(0, 25, 0);
+    camera.lookAt(scene.position);
+
+    // ground
+
+    const groundGeometry = new THREE.PlaneGeometry(20, 20, 10, 10);
+    const groundMaterial = new THREE.MeshBasicMaterial({ color: 0xe7e7e7 });
+    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
+    ground.rotation.x = Math.PI * -0.5;
+    scene.add(ground);
+
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 16;
+        map.repeat.set(4, 4);
+        map.colorSpace = THREE.SRGBColorSpace;
+        groundMaterial.map = map;
+        groundMaterial.needsUpdate = true;
+    });
+
+    // water
+
+    const waterGeometry = new THREE.PlaneGeometry(20, 20);
+    const flowMap = textureLoader.load('textures/water/Water_1_M_Flow.jpg');
+
+    water = new Water(waterGeometry, {
+        scale: 2,
+        textureWidth: 1024,
+        textureHeight: 1024,
+        flowMap: flowMap,
+    });
+
+    water.position.y = 1;
+    water.rotation.x = Math.PI * -0.5;
+    scene.add(water);
+
+    // flow map helper
+
+    const helperGeometry = new THREE.PlaneGeometry(20, 20);
+    const helperMaterial = new THREE.MeshBasicMaterial({ map: flowMap });
+    const helper = new THREE.Mesh(helperGeometry, helperMaterial);
+    helper.position.y = 1.01;
+    helper.rotation.x = Math.PI * -0.5;
+    helper.visible = false;
+    scene.add(helper);
+
+    // renderer
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const gui = new GUI();
+    gui.add(helper, 'visible').name('Show Flow Map');
+    gui.open();
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 5;
+    controls.maxDistance = 50;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_backdrop_area.ts b/examples-testing/examples/webgpu_backdrop_area.ts
new file mode 100644
index 000000000..97c224cea
--- /dev/null
+++ b/examples-testing/examples/webgpu_backdrop_area.ts
@@ -0,0 +1,164 @@
+import * as THREE from 'three';
+import {
+    color,
+    linearDepth,
+    viewportLinearDepth,
+    viewportSharedTexture,
+    textureBicubic,
+    viewportMipTexture,
+    viewportUV,
+    checker,
+    uv,
+    modelScale,
+} from 'three/tsl';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+let mixer, clock;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 25);
+    camera.position.set(3, 2, 3);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x777777);
+    camera.lookAt(0, 1, 0);
+
+    clock = new THREE.Clock();
+
+    const light = new THREE.PointLight(0xffffff, 50);
+    camera.add(light);
+    scene.add(camera);
+
+    const ambient = new THREE.AmbientLight(0x4466ff, 1);
+    scene.add(ambient);
+
+    // model
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/Michelle.glb', function (gltf) {
+        const object = gltf.scene;
+        mixer = new THREE.AnimationMixer(object);
+
+        const action = mixer.clipAction(gltf.animations[0]);
+        action.play();
+
+        scene.add(object);
+    });
+
+    // volume
+
+    // compare depth from viewportLinearDepth with linearDepth() to create a distance field
+    // viewportLinearDepth return the linear depth of the scene
+    // linearDepth() returns the linear depth of the mesh
+    const depthDistance = viewportLinearDepth.distance(linearDepth());
+
+    const depthAlphaNode = depthDistance.oneMinus().smoothstep(0.9, 2).mul(10).saturate();
+    const depthBlurred = textureBicubic(
+        viewportMipTexture(),
+        depthDistance
+            .smoothstep(0, 0.6)
+            .mul(40 * 5)
+            .clamp(0, 5),
+    );
+
+    const blurredBlur = new THREE.MeshBasicNodeMaterial();
+    blurredBlur.backdropNode = depthBlurred.add(depthAlphaNode.mix(color(0x0066ff), 0));
+    blurredBlur.transparent = true;
+    blurredBlur.side = THREE.DoubleSide;
+
+    const volumeMaterial = new THREE.MeshBasicNodeMaterial();
+    volumeMaterial.colorNode = color(0x0066ff);
+    volumeMaterial.backdropNode = viewportSharedTexture();
+    volumeMaterial.backdropAlphaNode = depthAlphaNode;
+    volumeMaterial.transparent = true;
+    volumeMaterial.side = THREE.DoubleSide;
+
+    const depthMaterial = new THREE.MeshBasicNodeMaterial();
+    depthMaterial.backdropNode = depthAlphaNode;
+    depthMaterial.transparent = true;
+    depthMaterial.side = THREE.DoubleSide;
+
+    const bicubicMaterial = new THREE.MeshBasicNodeMaterial();
+    bicubicMaterial.backdropNode = textureBicubic(viewportMipTexture(), 5); // @TODO: Move to alpha value [ 0, 1 ]
+    bicubicMaterial.backdropAlphaNode = checker(uv().mul(3).mul(modelScale.xy));
+    bicubicMaterial.opacityNode = bicubicMaterial.backdropAlphaNode;
+    bicubicMaterial.transparent = true;
+    bicubicMaterial.side = THREE.DoubleSide;
+
+    const pixelMaterial = new THREE.MeshBasicNodeMaterial();
+    pixelMaterial.backdropNode = viewportSharedTexture(viewportUV.mul(100).floor().div(100));
+    pixelMaterial.transparent = true;
+
+    // box / floor
+
+    const box = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), volumeMaterial);
+    box.position.set(0, 1, 0);
+    scene.add(box);
+
+    const floor = new THREE.Mesh(
+        new THREE.BoxGeometry(1.99, 0.01, 1.99),
+        new THREE.MeshBasicNodeMaterial({ color: 0x333333 }),
+    );
+    floor.position.set(0, 0, 0);
+    scene.add(floor);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer(/*{ antialias: true }*/);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.LinearToneMapping;
+    renderer.toneMappingExposure = 0.2;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 1, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+
+    // gui
+
+    const materials = {
+        blurred: blurredBlur,
+        volume: volumeMaterial,
+        depth: depthMaterial,
+        bicubic: bicubicMaterial,
+        pixel: pixelMaterial,
+    };
+
+    const gui = new GUI();
+    const options = { material: 'blurred' };
+
+    box.material = materials[options.material];
+
+    gui.add(box.scale, 'x', 0.1, 2, 0.01);
+    gui.add(box.scale, 'z', 0.1, 2, 0.01);
+    gui.add(options, 'material', Object.keys(materials)).onChange(name => {
+        box.material = materials[name];
+    });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) mixer.update(delta);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts b/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
new file mode 100644
index 000000000..155276322
--- /dev/null
+++ b/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
@@ -0,0 +1,245 @@
+import * as THREE from 'three';
+
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+// 1 micrometer to 100 billion light years in one scene, with 1 unit = 1 meter?  preposterous!  and yet...
+const NEAR = 1e-6,
+    FAR = 1e27;
+let SCREEN_WIDTH = window.innerWidth;
+let SCREEN_HEIGHT = window.innerHeight;
+let screensplit = 0.25,
+    screensplit_right = 0;
+const mouse = [0.5, 0.5];
+let zoompos = -100,
+    minzoomspeed = 0.015;
+let zoomspeed = minzoomspeed;
+
+let container, border, stats;
+const objects = {};
+
+// Generate a number of text labels, from 1µm in size up to 100,000,000 light years
+// Try to use some descriptive real-world examples of objects at each scale
+
+const labeldata = [
+    { size: 0.01, scale: 0.0001, label: 'microscopic (1µm)' }, // FIXME - triangulating text fails at this size, so we scale instead
+    { size: 0.01, scale: 0.1, label: 'minuscule (1mm)' },
+    { size: 0.01, scale: 1.0, label: 'tiny (1cm)' },
+    { size: 1, scale: 1.0, label: 'child-sized (1m)' },
+    { size: 10, scale: 1.0, label: 'tree-sized (10m)' },
+    { size: 100, scale: 1.0, label: 'building-sized (100m)' },
+    { size: 1000, scale: 1.0, label: 'medium (1km)' },
+    { size: 10000, scale: 1.0, label: 'city-sized (10km)' },
+    { size: 3400000, scale: 1.0, label: 'moon-sized (3,400 Km)' },
+    { size: 12000000, scale: 1.0, label: 'planet-sized (12,000 km)' },
+    { size: 1400000000, scale: 1.0, label: 'sun-sized (1,400,000 km)' },
+    { size: 7.47e12, scale: 1.0, label: 'solar system-sized (50Au)' },
+    { size: 9.4605284e15, scale: 1.0, label: 'gargantuan (1 light year)' },
+    { size: 3.08567758e16, scale: 1.0, label: 'ludicrous (1 parsec)' },
+    { size: 1e19, scale: 1.0, label: 'mind boggling (1000 light years)' },
+];
+
+init().then(animate);
+
+async function init() {
+    container = document.getElementById('container');
+
+    const loader = new FontLoader();
+    const font = await loader.loadAsync('fonts/helvetiker_regular.typeface.json');
+
+    const scene = initScene(font);
+
+    // Initialize two copies of the same scene, one with normal z-buffer and one with logarithmic z-buffer
+    objects.normal = await initView(scene, 'normal', false);
+    objects.logzbuf = await initView(scene, 'logzbuf', true);
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
+    border = document.getElementById('renderer_border');
+    border.addEventListener('pointerdown', onBorderPointerDown);
+
+    window.addEventListener('mousemove', onMouseMove);
+    window.addEventListener('resize', onWindowResize);
+    window.addEventListener('wheel', onMouseWheel);
+}
+
+async function initView(scene, name, logDepthBuf) {
+    const framecontainer = document.getElementById('container_' + name);
+
+    const camera = new THREE.PerspectiveCamera(50, (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT, NEAR, FAR);
+    scene.add(camera);
+
+    const renderer = new THREE.WebGPURenderer({ antialias: true, logarithmicDepthBuffer: logDepthBuf });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH / 2, SCREEN_HEIGHT);
+    renderer.domElement.style.position = 'relative';
+    renderer.domElement.id = 'renderer_' + name;
+    framecontainer.appendChild(renderer.domElement);
+
+    await renderer.init();
+
+    return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
+}
+
+function initScene(font) {
+    const scene = new THREE.Scene();
+
+    scene.add(new THREE.AmbientLight(0x777777));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(100, 100, 100);
+    scene.add(light);
+
+    const materialargs = {
+        color: 0xffffff,
+        specular: 0x050505,
+        shininess: 50,
+        emissive: 0x000000,
+    };
+
+    const geometry = new THREE.SphereGeometry(0.5, 24, 12);
+
+    for (let i = 0; i < labeldata.length; i++) {
+        const scale = labeldata[i].scale || 1;
+
+        const labelgeo = new TextGeometry(labeldata[i].label, {
+            font: font,
+            size: labeldata[i].size,
+            depth: labeldata[i].size / 2,
+        });
+
+        labelgeo.computeBoundingSphere();
+
+        // center text
+        labelgeo.translate(-labelgeo.boundingSphere.radius, 0, 0);
+
+        materialargs.color = new THREE.Color().setHSL(Math.random(), 0.5, 0.5);
+
+        const material = new THREE.MeshPhongMaterial(materialargs);
+
+        const group = new THREE.Group();
+        group.position.z = -labeldata[i].size * scale;
+        scene.add(group);
+
+        const textmesh = new THREE.Mesh(labelgeo, material);
+        textmesh.scale.set(scale, scale, scale);
+        textmesh.position.z = -labeldata[i].size * scale;
+        textmesh.position.y = (labeldata[i].size / 4) * scale;
+        group.add(textmesh);
+
+        const dotmesh = new THREE.Mesh(geometry, material);
+        dotmesh.position.y = (-labeldata[i].size / 4) * scale;
+        dotmesh.scale.multiplyScalar(labeldata[i].size * scale);
+        group.add(dotmesh);
+    }
+
+    return scene;
+}
+
+function updateRendererSizes() {
+    // Recalculate size for both renderers when screen size or split location changes
+
+    SCREEN_WIDTH = window.innerWidth;
+    SCREEN_HEIGHT = window.innerHeight;
+
+    screensplit_right = 1 - screensplit;
+
+    objects.normal.renderer.setSize(screensplit * SCREEN_WIDTH, SCREEN_HEIGHT);
+    objects.normal.camera.aspect = (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT;
+    objects.normal.camera.updateProjectionMatrix();
+    objects.normal.camera.setViewOffset(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH * screensplit, SCREEN_HEIGHT);
+    objects.normal.container.style.width = screensplit * 100 + '%';
+
+    objects.logzbuf.renderer.setSize(screensplit_right * SCREEN_WIDTH, SCREEN_HEIGHT);
+    objects.logzbuf.camera.aspect = (screensplit_right * SCREEN_WIDTH) / SCREEN_HEIGHT;
+    objects.logzbuf.camera.updateProjectionMatrix();
+    objects.logzbuf.camera.setViewOffset(
+        SCREEN_WIDTH,
+        SCREEN_HEIGHT,
+        SCREEN_WIDTH * screensplit,
+        0,
+        SCREEN_WIDTH * screensplit_right,
+        SCREEN_HEIGHT,
+    );
+    objects.logzbuf.container.style.width = screensplit_right * 100 + '%';
+
+    border.style.left = screensplit * 100 + '%';
+}
+
+function animate() {
+    requestAnimationFrame(animate);
+
+    // Put some limits on zooming
+    const minzoom = labeldata[0].size * labeldata[0].scale * 1;
+    const maxzoom = labeldata[labeldata.length - 1].size * labeldata[labeldata.length - 1].scale * 100;
+    let damping = Math.abs(zoomspeed) > minzoomspeed ? 0.95 : 1.0;
+
+    // Zoom out faster the further out you go
+    const zoom = THREE.MathUtils.clamp(Math.pow(Math.E, zoompos), minzoom, maxzoom);
+    zoompos = Math.log(zoom);
+
+    // Slow down quickly at the zoom limits
+    if ((zoom == minzoom && zoomspeed < 0) || (zoom == maxzoom && zoomspeed > 0)) {
+        damping = 0.85;
+    }
+
+    zoompos += zoomspeed;
+    zoomspeed *= damping;
+
+    objects.normal.camera.position.x = Math.sin(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
+    objects.normal.camera.position.y = Math.sin(0.25 * Math.PI * (mouse[1] - 0.5)) * zoom;
+    objects.normal.camera.position.z = Math.cos(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
+    objects.normal.camera.lookAt(objects.normal.scene.position);
+
+    // Clone camera settings across both scenes
+    objects.logzbuf.camera.position.copy(objects.normal.camera.position);
+    objects.logzbuf.camera.quaternion.copy(objects.normal.camera.quaternion);
+
+    // Update renderer sizes if the split has changed
+    if (screensplit_right != 1 - screensplit) {
+        updateRendererSizes();
+    }
+
+    objects.normal.renderer.render(objects.normal.scene, objects.normal.camera);
+    objects.logzbuf.renderer.render(objects.logzbuf.scene, objects.logzbuf.camera);
+
+    stats.update();
+}
+
+function onWindowResize() {
+    updateRendererSizes();
+}
+
+function onBorderPointerDown() {
+    // activate draggable window resizing bar
+    window.addEventListener('pointermove', onBorderPointerMove);
+    window.addEventListener('pointerup', onBorderPointerUp);
+}
+
+function onBorderPointerMove(ev) {
+    screensplit = Math.max(0, Math.min(1, ev.clientX / window.innerWidth));
+}
+
+function onBorderPointerUp() {
+    window.removeEventListener('pointermove', onBorderPointerMove);
+    window.removeEventListener('pointerup', onBorderPointerUp);
+}
+
+function onMouseMove(ev) {
+    mouse[0] = ev.clientX / window.innerWidth;
+    mouse[1] = ev.clientY / window.innerHeight;
+}
+
+function onMouseWheel(ev) {
+    const amount = ev.deltaY;
+    if (amount === 0) return;
+    const dir = amount / Math.abs(amount);
+    zoomspeed = dir / 10;
+
+    // Slow down default zoom speed after user starts zooming, to give them more control
+    minzoomspeed = 0.001;
+}
diff --git a/examples-testing/examples/webgpu_clearcoat.ts b/examples-testing/examples/webgpu_clearcoat.ts
new file mode 100644
index 000000000..0d5b70a2f
--- /dev/null
+++ b/examples-testing/examples/webgpu_clearcoat.ts
@@ -0,0 +1,205 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
+
+import { FlakesTexture } from 'three/addons/textures/FlakesTexture.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+
+let particleLight;
+let group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.25, 50);
+    camera.position.z = 10;
+
+    scene = new THREE.Scene();
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    new HDRCubeTextureLoader()
+        .setPath('textures/cube/pisaHDR/')
+        .load(['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'], function (texture) {
+            const geometry = new THREE.SphereGeometry(0.8, 64, 32);
+
+            const textureLoader = new THREE.TextureLoader();
+
+            const diffuse = textureLoader.load('textures/carbon/Carbon.png');
+            diffuse.colorSpace = THREE.SRGBColorSpace;
+            diffuse.wrapS = THREE.RepeatWrapping;
+            diffuse.wrapT = THREE.RepeatWrapping;
+            diffuse.repeat.x = 10;
+            diffuse.repeat.y = 10;
+
+            const normalMap = textureLoader.load('textures/carbon/Carbon_Normal.png');
+            normalMap.wrapS = THREE.RepeatWrapping;
+            normalMap.wrapT = THREE.RepeatWrapping;
+            normalMap.repeat.x = 10;
+            normalMap.repeat.y = 10;
+
+            const normalMap2 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
+
+            const normalMap3 = new THREE.CanvasTexture(new FlakesTexture());
+            normalMap3.wrapS = THREE.RepeatWrapping;
+            normalMap3.wrapT = THREE.RepeatWrapping;
+            normalMap3.repeat.x = 10;
+            normalMap3.repeat.y = 6;
+            normalMap3.anisotropy = 16;
+
+            const normalMap4 = textureLoader.load('textures/golfball.jpg');
+
+            const clearcoatNormalMap = textureLoader.load(
+                'textures/pbr/Scratched_gold/Scratched_gold_01_1K_Normal.png',
+            );
+
+            // car paint
+
+            let material = new THREE.MeshPhysicalMaterial({
+                clearcoat: 1.0,
+                clearcoatRoughness: 0.1,
+                metalness: 0.9,
+                roughness: 0.5,
+                color: 0x0000ff,
+                normalMap: normalMap3,
+                normalScale: new THREE.Vector2(0.15, 0.15),
+            });
+            let mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = -1;
+            mesh.position.y = 1;
+            group.add(mesh);
+
+            // fibers
+
+            material = new THREE.MeshPhysicalMaterial({
+                roughness: 0.5,
+                clearcoat: 1.0,
+                clearcoatRoughness: 0.1,
+                map: diffuse,
+                normalMap: normalMap,
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = 1;
+            mesh.position.y = 1;
+            group.add(mesh);
+
+            // golf
+
+            material = new THREE.MeshPhysicalMaterial({
+                metalness: 0.0,
+                roughness: 0.1,
+                clearcoat: 1.0,
+                normalMap: normalMap4,
+                clearcoatNormalMap: clearcoatNormalMap,
+
+                // y scale is negated to compensate for normal map handedness.
+                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = -1;
+            mesh.position.y = -1;
+            group.add(mesh);
+
+            // clearcoat + normalmap
+
+            material = new THREE.MeshPhysicalMaterial({
+                clearcoat: 1.0,
+                metalness: 1.0,
+                color: 0xff0000,
+                normalMap: normalMap2,
+                normalScale: new THREE.Vector2(0.15, 0.15),
+                clearcoatNormalMap: clearcoatNormalMap,
+
+                // y scale is negated to compensate for normal map handedness.
+                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            mesh.position.x = 1;
+            mesh.position.y = -1;
+            group.add(mesh);
+
+            //
+
+            scene.background = texture;
+            scene.environment = texture;
+        });
+
+    // LIGHTS
+
+    particleLight = new THREE.Mesh(
+        new THREE.SphereGeometry(0.05, 8, 8),
+        new THREE.MeshBasicMaterial({ color: 0xffffff }),
+    );
+    scene.add(particleLight);
+
+    particleLight.add(new THREE.PointLight(0xffffff, 30));
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1.25;
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // EVENTS
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 3;
+    controls.maxDistance = 30;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+//
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+//
+
+function animate() {
+    render();
+
+    stats.update();
+}
+
+function render() {
+    const timer = Date.now() * 0.00025;
+
+    particleLight.position.x = Math.sin(timer * 7) * 3;
+    particleLight.position.y = Math.cos(timer * 5) * 4;
+    particleLight.position.z = Math.cos(timer * 3) * 3;
+
+    for (let i = 0; i < group.children.length; i++) {
+        const child = group.children[i];
+        child.rotation.y += 0.005;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_clipping.ts b/examples-testing/examples/webgpu_clipping.ts
new file mode 100644
index 000000000..e57a7e96c
--- /dev/null
+++ b/examples-testing/examples/webgpu_clipping.ts
@@ -0,0 +1,207 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, startTime, object, stats;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
+
+    camera.position.set(0, 1.3, 3);
+
+    scene = new THREE.Scene();
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const spotLight = new THREE.SpotLight(0xffffff, 60);
+    spotLight.angle = Math.PI / 5;
+    spotLight.penumbra = 0.2;
+    spotLight.position.set(2, 3, 3);
+    spotLight.castShadow = true;
+    spotLight.shadow.camera.near = 3;
+    spotLight.shadow.camera.far = 10;
+    spotLight.shadow.mapSize.width = 2048;
+    spotLight.shadow.mapSize.height = 2048;
+    spotLight.shadow.bias = -0.002;
+    spotLight.shadow.radius = 4;
+    scene.add(spotLight);
+
+    const dirLight = new THREE.DirectionalLight(0x55505a, 3);
+    dirLight.position.set(0, 3, 0);
+    dirLight.castShadow = true;
+    dirLight.shadow.camera.near = 1;
+    dirLight.shadow.camera.far = 10;
+
+    dirLight.shadow.camera.right = 1;
+    dirLight.shadow.camera.left = -1;
+    dirLight.shadow.camera.top = 1;
+    dirLight.shadow.camera.bottom = -1;
+
+    dirLight.shadow.mapSize.width = 1024;
+    dirLight.shadow.mapSize.height = 1024;
+    scene.add(dirLight);
+
+    // ***** Clipping planes: *****
+
+    const localPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.8);
+    const localPlane2 = new THREE.Plane(new THREE.Vector3(0, 0, -1), 0.1);
+    const globalPlane = new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0.1);
+
+    // Geometry
+
+    const material = new THREE.MeshPhongNodeMaterial({
+        color: 0x80ee10,
+        shininess: 0,
+        side: THREE.DoubleSide,
+
+        // ***** Clipping setup (material): *****
+        clippingPlanes: [localPlane, localPlane2],
+        clipShadows: true,
+        alphaToCoverage: true,
+        clipIntersection: true,
+    });
+
+    const geometry = new THREE.TorusKnotGeometry(0.4, 0.08, 95, 20);
+
+    object = new THREE.Mesh(geometry, material);
+    object.castShadow = true;
+    scene.add(object);
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(9, 9, 1, 1),
+        new THREE.MeshPhongNodeMaterial({ color: 0xa0adaf, shininess: 150 }),
+    );
+
+    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
+    ground.receiveShadow = true;
+    scene.add(ground);
+
+    // Stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // Renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.shadowMap.enabled = true;
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    window.addEventListener('resize', onWindowResize);
+    document.body.appendChild(renderer.domElement);
+
+    // ***** Clipping setup (renderer): *****
+    const globalPlanes = [globalPlane];
+    const Empty = Object.freeze([]);
+
+    renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
+    renderer.localClippingEnabled = true;
+
+    // Controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 1, 0);
+    controls.update();
+
+    // GUI
+
+    const gui = new GUI(),
+        props = {
+            alphaToCoverage: true,
+        },
+        folderLocal = gui.addFolder('Local Clipping'),
+        propsLocal = {
+            get Enabled() {
+                return renderer.localClippingEnabled;
+            },
+            set Enabled(v) {
+                renderer.localClippingEnabled = v;
+            },
+
+            get Shadows() {
+                return material.clipShadows;
+            },
+            set Shadows(v) {
+                material.clipShadows = v;
+            },
+
+            get Intersection() {
+                return material.clipIntersection;
+            },
+
+            set Intersection(v) {
+                material.clipIntersection = v;
+            },
+
+            get Plane() {
+                return localPlane.constant;
+            },
+            set Plane(v) {
+                localPlane.constant = v;
+            },
+        },
+        folderGlobal = gui.addFolder('Global Clipping'),
+        propsGlobal = {
+            get Enabled() {
+                return renderer.clippingPlanes !== Empty;
+            },
+            set Enabled(v) {
+                renderer.clippingPlanes = v ? globalPlanes : Empty;
+            },
+
+            get Plane() {
+                return globalPlane.constant;
+            },
+            set Plane(v) {
+                globalPlane.constant = v;
+            },
+        };
+
+    gui.add(props, 'alphaToCoverage').onChange(function (value) {
+        ground.material.alphaToCoverage = value;
+        ground.material.needsUpdate = true;
+
+        material.alphaToCoverage = value;
+        material.needsUpdate = true;
+    });
+
+    folderLocal.add(propsLocal, 'Enabled');
+    folderLocal.add(propsLocal, 'Shadows');
+    folderLocal.add(propsLocal, 'Intersection');
+    folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);
+
+    folderGlobal.add(propsGlobal, 'Enabled');
+    folderGlobal.add(propsGlobal, 'Plane', -0.4, 3);
+
+    // Start
+
+    startTime = Date.now();
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate(currentTime) {
+    const time = (currentTime - startTime) / 1000;
+
+    object.position.y = 0.8;
+    object.rotation.x = time * 0.5;
+    object.rotation.y = time * 0.2;
+    object.scale.setScalar(Math.cos(time) * 0.125 + 0.875);
+
+    stats.begin();
+    renderer.render(scene, camera);
+    stats.end();
+}
diff --git a/examples-testing/examples/webgpu_custom_fog_background.ts b/examples-testing/examples/webgpu_custom_fog_background.ts
new file mode 100644
index 000000000..4a2e6c800
--- /dev/null
+++ b/examples-testing/examples/webgpu_custom_fog_background.ts
@@ -0,0 +1,93 @@
+import * as THREE from 'three';
+import { pass, color, rangeFog } from 'three/tsl';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer;
+let postProcessing;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    //renderer.toneMapping = THREE.ACESFilmicToneMapping; // apply tone mapping in post processing
+    container.appendChild(renderer.domElement);
+
+    // post processing
+
+    // render scene pass ( the same of css )
+    const scenePass = pass(scene, camera);
+    const scenePassViewZ = scenePass.getViewZNode();
+
+    // background color
+    const backgroundColor = color(0x0066ff);
+
+    // get fog factor from scene pass context
+    // equivalent to: scene.fog = new THREE.Fog( 0x0066ff, 2.7, 4 );
+    const fogFactor = rangeFog(null, 2.7, 4).context({ getViewZ: () => scenePassViewZ });
+
+    // tone mapping scene pass
+    const scenePassTM = scenePass.toneMapping(THREE.ACESFilmicToneMapping);
+
+    // mix fog from fog factor and background color
+    const compose = fogFactor.mix(scenePassTM, backgroundColor);
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    postProcessing.outputNode = compose;
+
+    //
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.environment = texture;
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', function (gltf) {
+            scene.add(gltf.scene);
+
+            render();
+        });
+    });
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 5;
+    controls.target.set(0, -0.1, -0.2);
+    controls.update();
+    controls.addEventListener('change', render); // use if there is no animation loop
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    postProcessing.renderAsync();
+}
diff --git a/examples-testing/examples/webgpu_display_stereo.ts b/examples-testing/examples/webgpu_display_stereo.ts
new file mode 100644
index 000000000..7eb45fa2a
--- /dev/null
+++ b/examples-testing/examples/webgpu_display_stereo.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+
+import { stereoPass, anaglyphPass, parallaxBarrierPass } from 'three/tsl';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Timer } from 'three/addons/misc/Timer.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, postProcessing;
+
+let stereo, anaglyph, parallaxBarrier;
+
+let mesh, dummy, timer;
+
+const position = new THREE.Vector3();
+
+const params = {
+    effect: 'stereo',
+    eyeSep: 0.064,
+};
+
+const effects = { Stereo: 'stereo', Anaglyph: 'anaglyph', ParallaxBarrier: 'parallaxBarrier' };
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 3;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.CubeTextureLoader()
+        .setPath('textures/cube/Park3Med/')
+        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+
+    timer = new Timer();
+
+    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
+
+    const textureCube = new THREE.CubeTextureLoader()
+        .setPath('textures/cube/Park3Med/')
+        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
+
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
+
+    mesh = new THREE.InstancedMesh(geometry, material, 500);
+    mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
+
+    dummy = new THREE.Mesh();
+
+    for (let i = 0; i < 500; i++) {
+        dummy.position.x = Math.random() * 10 - 5;
+        dummy.position.y = Math.random() * 10 - 5;
+        dummy.position.z = Math.random() * 10 - 5;
+        dummy.scale.x = dummy.scale.y = dummy.scale.z = Math.random() * 3 + 1;
+
+        dummy.updateMatrix();
+
+        mesh.setMatrixAt(i, dummy.matrix);
+    }
+
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    stereo = stereoPass(scene, camera);
+    anaglyph = anaglyphPass(scene, camera);
+    parallaxBarrier = parallaxBarrierPass(scene, camera);
+
+    postProcessing.outputNode = stereo;
+
+    const gui = new GUI();
+    gui.add(params, 'effect', effects).onChange(update);
+    gui.add(params, 'eyeSep', 0.001, 0.15, 0.001).onChange(function (value) {
+        stereo.stereo.eyeSep = value;
+
+        anaglyph.stereo.eyeSep = value;
+        parallaxBarrier.stereo.eyeSep = value;
+    });
+
+    window.addEventListener('resize', onWindowResize);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 25;
+}
+
+function update(value) {
+    if (value === 'stereo') {
+        postProcessing.outputNode = stereo;
+    } else if (value === 'anaglyph') {
+        postProcessing.outputNode = anaglyph;
+    } else if (value === 'parallaxBarrier') {
+        postProcessing.outputNode = parallaxBarrier;
+    }
+
+    postProcessing.needsUpdate = true;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function extractPosition(matrix, position) {
+    position.x = matrix.elements[12];
+    position.y = matrix.elements[13];
+    position.z = matrix.elements[14];
+}
+
+function animate() {
+    timer.update();
+
+    const elapsedTime = timer.getElapsed() * 0.1;
+
+    for (let i = 0; i < mesh.count; i++) {
+        mesh.getMatrixAt(i, dummy.matrix);
+
+        extractPosition(dummy.matrix, position);
+
+        position.x = 5 * Math.cos(elapsedTime + i);
+        position.y = 5 * Math.sin(elapsedTime + i * 1.1);
+
+        dummy.matrix.setPosition(position);
+
+        mesh.setMatrixAt(i, dummy.matrix);
+
+        mesh.instanceMatrix.needsUpdate = true;
+    }
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_instancing_morph.ts b/examples-testing/examples/webgpu_instancing_morph.ts
new file mode 100644
index 000000000..cfd721721
--- /dev/null
+++ b/examples-testing/examples/webgpu_instancing_morph.ts
@@ -0,0 +1,148 @@
+import * as THREE from 'three';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer, stats, mesh, mixer, dummy;
+
+const offset = 5000;
+
+const timeOffsets = new Float32Array(1024);
+
+for (let i = 0; i < 1024; i++) {
+    timeOffsets[i] = Math.random() * 3;
+}
+
+const clock = new THREE.Clock(true);
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 10000);
+
+    scene = new THREE.Scene();
+
+    scene.background = new THREE.Color(0x99ddff);
+
+    scene.fog = new THREE.Fog(0x99ddff, 5000, 10000);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    const light = new THREE.DirectionalLight(0xffffff, 1);
+
+    light.position.set(200, 1000, 50);
+
+    light.shadow.mapSize.width = 2048;
+    light.shadow.mapSize.height = 2048;
+    light.castShadow = true;
+
+    light.shadow.camera.left = -5000;
+    light.shadow.camera.right = 5000;
+    light.shadow.camera.top = 5000;
+    light.shadow.camera.bottom = -5000;
+    light.shadow.camera.far = 2000;
+
+    light.shadow.bias = -0.01;
+
+    light.shadow.camera.updateProjectionMatrix();
+
+    scene.add(light);
+
+    const hemi = new THREE.HemisphereLight(0x99ddff, 0x669933, 1 / 3);
+
+    scene.add(hemi);
+
+    const ground = new THREE.Mesh(
+        new THREE.PlaneGeometry(1000000, 1000000),
+        new THREE.MeshStandardMaterial({ color: 0x669933 }),
+    );
+
+    ground.rotation.x = -Math.PI / 2;
+
+    ground.receiveShadow = true;
+
+    scene.add(ground);
+
+    const loader = new GLTFLoader();
+
+    loader.load('models/gltf/Horse.glb', function (glb) {
+        dummy = glb.scene.children[0];
+
+        mesh = new THREE.InstancedMesh(
+            dummy.geometry,
+            new THREE.MeshStandardNodeMaterial({
+                flatShading: true,
+            }),
+            1024,
+        );
+
+        mesh.castShadow = true;
+
+        for (let x = 0, i = 0; x < 32; x++) {
+            for (let y = 0; y < 32; y++) {
+                dummy.position.set(offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y);
+
+                dummy.updateMatrix();
+
+                mesh.setMatrixAt(i, dummy.matrix);
+
+                mesh.setColorAt(i, new THREE.Color(`hsl(${Math.random() * 360}, 50%, 66%)`));
+
+                i++;
+            }
+        }
+
+        scene.add(mesh);
+
+        mixer = new THREE.AnimationMixer(glb.scene);
+
+        const action = mixer.clipAction(glb.animations[0]);
+
+        action.play();
+    });
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setAnimationLoop(animate);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const time = clock.getElapsedTime();
+
+    const r = 3000;
+    camera.position.set(Math.sin(time / 10) * r, 1500 + 1000 * Math.cos(time / 5), Math.cos(time / 10) * r);
+    camera.lookAt(0, 0, 0);
+
+    if (mesh) {
+        for (let i = 0; i < 1024; i++) {
+            mixer.setTime(time + timeOffsets[i]);
+
+            mesh.setMorphAt(i, dummy);
+        }
+
+        mesh.morphTexture.needsUpdate = true;
+    }
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_lightprobe.ts b/examples-testing/examples/webgpu_lightprobe.ts
new file mode 100644
index 000000000..a34ad2dec
--- /dev/null
+++ b/examples-testing/examples/webgpu_lightprobe.ts
@@ -0,0 +1,126 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
+
+let mesh, renderer, scene, camera;
+
+let gui;
+
+let lightProbe;
+let directionalLight;
+
+// linear color space
+const API = {
+    lightProbeIntensity: 1.0,
+    directionalLightIntensity: 0.6,
+    envMapIntensity: 1,
+};
+
+init();
+
+function init() {
+    // renderer
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // tone mapping
+    renderer.toneMapping = THREE.NoToneMapping;
+
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 0, 30);
+
+    // controls
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 10;
+    controls.maxDistance = 50;
+    controls.enablePan = false;
+
+    // probe
+    lightProbe = new THREE.LightProbe();
+    scene.add(lightProbe);
+
+    // light
+    directionalLight = new THREE.DirectionalLight(0xffffff, API.directionalLightIntensity);
+    directionalLight.position.set(10, 10, 10);
+    scene.add(directionalLight);
+
+    // envmap
+    const genCubeUrls = function (prefix, postfix) {
+        return [
+            prefix + 'px' + postfix,
+            prefix + 'nx' + postfix,
+            prefix + 'py' + postfix,
+            prefix + 'ny' + postfix,
+            prefix + 'pz' + postfix,
+            prefix + 'nz' + postfix,
+        ];
+    };
+
+    const urls = genCubeUrls('textures/cube/pisa/', '.png');
+
+    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
+        scene.background = cubeTexture;
+
+        lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture));
+
+        const geometry = new THREE.SphereGeometry(5, 64, 32);
+        //const geometry = new THREE.TorusKnotGeometry( 4, 1.5, 256, 32, 2, 3 );
+
+        const material = new THREE.MeshStandardMaterial({
+            color: 0xffffff,
+            metalness: 0,
+            roughness: 0,
+            envMap: cubeTexture,
+            envMapIntensity: API.envMapIntensity,
+        });
+
+        // mesh
+        mesh = new THREE.Mesh(geometry, material);
+        scene.add(mesh);
+    });
+
+    // gui
+    gui = new GUI({ title: 'Intensity' });
+
+    gui.add(API, 'lightProbeIntensity', 0, 1, 0.02)
+        .name('light probe')
+        .onChange(function () {
+            lightProbe.intensity = API.lightProbeIntensity;
+        });
+
+    gui.add(API, 'directionalLightIntensity', 0, 1, 0.02)
+        .name('directional light')
+        .onChange(function () {
+            directionalLight.intensity = API.directionalLightIntensity;
+        });
+
+    gui.add(API, 'envMapIntensity', 0, 1, 0.02)
+        .name('envMap')
+        .onChange(function () {
+            mesh.material.envMapIntensity = API.envMapIntensity;
+        });
+
+    // listener
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_lights_ies_spotlight.ts b/examples-testing/examples/webgpu_lights_ies_spotlight.ts
new file mode 100644
index 000000000..41b56de88
--- /dev/null
+++ b/examples-testing/examples/webgpu_lights_ies_spotlight.ts
@@ -0,0 +1,117 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+import { IESLoader } from 'three/addons/loaders/IESLoader.js';
+
+let renderer, scene, camera;
+let lights;
+
+async function init() {
+    const iesLoader = new IESLoader().setPath('./ies/');
+    //iesLoader.type = THREE.UnsignedByteType; // LDR
+
+    const [iesTexture1, iesTexture2, iesTexture3, iesTexture4] = await Promise.all([
+        iesLoader.loadAsync('007cfb11e343e2f42e3b476be4ab684e.ies'),
+        iesLoader.loadAsync('06b4cfdc8805709e767b5e2e904be8ad.ies'),
+        iesLoader.loadAsync('02a7562c650498ebb301153dbbf59207.ies'),
+        iesLoader.loadAsync('1a936937a49c63374e6d4fbed9252b29.ies'),
+    ]);
+
+    //
+
+    scene = new THREE.Scene();
+
+    //
+
+    const spotLight = new THREE.IESSpotLight(0xff0000, 500);
+    spotLight.position.set(6.5, 1.5, 6.5);
+    spotLight.angle = Math.PI / 8;
+    spotLight.penumbra = 0.7;
+    spotLight.distance = 20;
+    spotLight.iesMap = iesTexture1;
+    scene.add(spotLight);
+
+    //
+
+    const spotLight2 = new THREE.IESSpotLight(0x00ff00, 500);
+    spotLight2.position.set(6.5, 1.5, -6.5);
+    spotLight2.angle = Math.PI / 8;
+    spotLight2.penumbra = 0.7;
+    spotLight2.distance = 20;
+    spotLight2.iesMap = iesTexture2;
+    scene.add(spotLight2);
+
+    //
+
+    const spotLight3 = new THREE.IESSpotLight(0x0000ff, 500);
+    spotLight3.position.set(-6.5, 1.5, -6.5);
+    spotLight3.angle = Math.PI / 8;
+    spotLight3.penumbra = 0.7;
+    spotLight3.distance = 20;
+    spotLight3.iesMap = iesTexture3;
+    scene.add(spotLight3);
+
+    //
+
+    const spotLight4 = new THREE.IESSpotLight(0xffffff, 500);
+    spotLight4.position.set(-6.5, 1.5, 6.5);
+    spotLight4.angle = Math.PI / 8;
+    spotLight4.penumbra = 0.7;
+    spotLight4.distance = 20;
+    spotLight4.iesMap = iesTexture4;
+    scene.add(spotLight4);
+
+    //
+
+    lights = [spotLight, spotLight2, spotLight3, spotLight4];
+
+    //
+
+    const material = new THREE.MeshPhongMaterial({ color: 0x808080 /*, dithering: true*/ });
+
+    const geometry = new THREE.PlaneGeometry(200, 200);
+
+    const mesh = new THREE.Mesh(geometry, material);
+    mesh.rotation.x = -Math.PI * 0.5;
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(16, 4, 1);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 50;
+    controls.enablePan = false;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render(time) {
+    time = (time / 1000) * 2.0;
+
+    for (let i = 0; i < lights.length; i++) {
+        lights[i].position.y = Math.sin(time + i) + 0.97;
+    }
+
+    renderer.render(scene, camera);
+}
+
+init();
diff --git a/examples-testing/examples/webgpu_lights_rectarealight.ts b/examples-testing/examples/webgpu_lights_rectarealight.ts
new file mode 100644
index 000000000..5638c9029
--- /dev/null
+++ b/examples-testing/examples/webgpu_lights_rectarealight.ts
@@ -0,0 +1,79 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
+import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js';
+
+let renderer, scene, camera;
+let stats, meshKnot;
+
+init();
+
+function init() {
+    THREE.RectAreaLightNode.setLTC(RectAreaLightTexturesLib.init());
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animation);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.set(0, 5, -15);
+
+    scene = new THREE.Scene();
+
+    const rectLight1 = new THREE.RectAreaLight(0xff0000, 5, 4, 10);
+    rectLight1.position.set(-5, 5, 5);
+    scene.add(rectLight1);
+
+    const rectLight2 = new THREE.RectAreaLight(0x00ff00, 5, 4, 10);
+    rectLight2.position.set(0, 5, 5);
+    scene.add(rectLight2);
+
+    const rectLight3 = new THREE.RectAreaLight(0x0000ff, 5, 4, 10);
+    rectLight3.position.set(5, 5, 5);
+    scene.add(rectLight3);
+
+    scene.add(new RectAreaLightHelper(rectLight1));
+    scene.add(new RectAreaLightHelper(rectLight2));
+    scene.add(new RectAreaLightHelper(rectLight3));
+
+    const geoFloor = new THREE.BoxGeometry(2000, 0.1, 2000);
+    const matStdFloor = new THREE.MeshStandardMaterial({ color: 0xbcbcbc, roughness: 0.1, metalness: 0 });
+    const mshStdFloor = new THREE.Mesh(geoFloor, matStdFloor);
+    scene.add(mshStdFloor);
+
+    const geoKnot = new THREE.TorusKnotGeometry(1.5, 0.5, 200, 16);
+    const matKnot = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0, metalness: 0 });
+    meshKnot = new THREE.Mesh(geoKnot, matKnot);
+    meshKnot.position.set(0, 5, 0);
+    scene.add(meshKnot);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.copy(meshKnot.position);
+    controls.update();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+}
+
+function animation(time) {
+    meshKnot.rotation.y = time / 1000;
+
+    renderer.render(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf.ts b/examples-testing/examples/webgpu_loader_gltf.ts
new file mode 100644
index 000000000..64d1fda4b
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf.ts
@@ -0,0 +1,71 @@
+import * as THREE from 'three';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+        //texture.minFilter = THREE.LinearMipmapLinearFilter;
+        //texture.generateMipmaps = true;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        render();
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', function (gltf) {
+            scene.add(gltf.scene);
+
+            render();
+        });
+    });
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.addEventListener('change', render); // use if there is no animation loop
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+//
+
+function render() {
+    renderer.renderAsync(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts b/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
new file mode 100644
index 000000000..d100e8c81
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
@@ -0,0 +1,65 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let renderer, scene, camera, controls;
+
+init();
+
+async function init() {
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1.35;
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.set(-0.35, -0.2, 0.35);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, -0.08, 0.11);
+    controls.minDistance = 0.1;
+    controls.maxDistance = 2;
+    controls.addEventListener('change', render);
+    controls.update();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
+
+    const [texture, gltf] = await Promise.all([
+        rgbeLoader.loadAsync('royal_esplanade_1k.hdr'),
+        gltfLoader.loadAsync('AnisotropyBarnLamp.glb'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.backgroundBlurriness = 0.5;
+    scene.environment = texture;
+
+    // model
+
+    scene.add(gltf.scene);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render() {
+    renderer.renderAsync(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_compressed.ts b/examples-testing/examples/webgpu_loader_gltf_compressed.ts
new file mode 100644
index 000000000..9405b64ae
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_compressed.ts
@@ -0,0 +1,67 @@
+import * as THREE from 'three';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+init();
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 20);
+    camera.position.set(2, 2, 2);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xeeeeee);
+
+    //lights
+
+    const light = new THREE.PointLight(0xffffff);
+    light.power = 1300;
+    camera.add(light);
+    scene.add(camera);
+
+    //renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    renderer.toneMappingExposure = 1;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 3;
+    controls.maxDistance = 6;
+    controls.update();
+
+    const ktx2Loader = await new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupportAsync(renderer);
+
+    const loader = new GLTFLoader();
+    loader.setKTX2Loader(ktx2Loader);
+    loader.setMeshoptDecoder(MeshoptDecoder);
+    loader.load('models/gltf/coffeemat.glb', function (gltf) {
+        const gltfScene = gltf.scene;
+        gltfScene.position.y = -0.8;
+        gltfScene.scale.setScalar(0.01);
+
+        scene.add(gltfScene);
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_dispersion.ts b/examples-testing/examples/webgpu_loader_gltf_dispersion.ts
new file mode 100644
index 000000000..c0290b98f
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_dispersion.ts
@@ -0,0 +1,63 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer;
+
+init();
+
+async function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 5);
+    camera.position.set(0.1, 0.05, 0.15);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    renderer.toneMapping = THREE.ReinhardToneMapping; // TODO: Add THREE.NeutralToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    const rgbeLoader = await new RGBELoader()
+        .setPath('textures/equirectangular/')
+        .loadAsync('pedestrian_overpass_1k.hdr');
+    rgbeLoader.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene = new THREE.Scene();
+    scene.backgroundBlurriness = 0.5;
+    scene.environment = rgbeLoader;
+    scene.background = rgbeLoader;
+
+    const loader = new GLTFLoader();
+    const gltf = await loader.loadAsync('models/gltf/DispersionTest.glb');
+
+    scene.add(gltf.scene);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 0.1;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_iridescence.ts b/examples-testing/examples/webgpu_loader_gltf_iridescence.ts
new file mode 100644
index 000000000..f163ea770
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_iridescence.ts
@@ -0,0 +1,70 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let renderer, scene, camera, controls;
+
+init().catch(function (err) {
+    console.error(err);
+});
+
+async function init() {
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setAnimationLoop(render);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 20);
+    camera.position.set(0.35, 0.05, 0.35);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -0.5;
+    controls.target.set(0, 0.2, 0);
+    controls.update();
+
+    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
+
+    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
+
+    const [texture, gltf] = await Promise.all([
+        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
+        gltfLoader.loadAsync('IridescenceLamp.glb'),
+    ]);
+
+    // environment
+
+    texture.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.background = texture;
+    scene.environment = texture;
+
+    // model
+
+    scene.add(gltf.scene);
+
+    render();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    render();
+}
+
+function render() {
+    controls.update();
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_sheen.ts b/examples-testing/examples/webgpu_loader_gltf_sheen.ts
new file mode 100644
index 000000000..788ef2a89
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_sheen.ts
@@ -0,0 +1,81 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, controls;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
+    camera.position.set(-0.75, 0.7, 1.25);
+
+    scene = new THREE.Scene();
+    //scene.add( new THREE.DirectionalLight( 0xffffff, 2 ) );
+
+    // model
+
+    new GLTFLoader().setPath('models/gltf/').load('SheenChair.glb', function (gltf) {
+        scene.add(gltf.scene);
+
+        const object = gltf.scene.getObjectByName('SheenChair_fabric');
+
+        const gui = new GUI();
+
+        gui.add(object.material, 'sheen', 0, 1);
+        gui.open();
+    });
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    scene.background = new THREE.Color(0xaaaaaa);
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        //scene.backgroundBlurriness = 1; // @TODO: Needs PMREM
+        scene.environment = texture;
+    });
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0.35, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    controls.update(); // required if damping enabled
+
+    render();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_loader_gltf_transmission.ts b/examples-testing/examples/webgpu_loader_gltf_transmission.ts
new file mode 100644
index 000000000..040233262
--- /dev/null
+++ b/examples-testing/examples/webgpu_loader_gltf_transmission.ts
@@ -0,0 +1,80 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+let camera, scene, renderer, controls, clock, mixer;
+
+init();
+
+function init() {
+    clock = new THREE.Clock();
+
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(0, 0.4, 0.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.backgroundBlurriness = 0.35;
+
+        scene.environment = texture;
+
+        // model
+
+        new GLTFLoader()
+            .setPath('models/gltf/')
+            .setDRACOLoader(new DRACOLoader().setDecoderPath('jsm/libs/draco/gltf/'))
+            .load('IridescentDishWithOlives.glb', function (gltf) {
+                mixer = new THREE.AnimationMixer(gltf.scene);
+                mixer.clipAction(gltf.animations[0]).play();
+
+                scene.add(gltf.scene);
+            });
+    });
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setAnimationLoop(render);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    container.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -0.75;
+    controls.enableDamping = true;
+    controls.minDistance = 0.5;
+    controls.maxDistance = 1;
+    controls.target.set(0, 0.1, 0);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    if (mixer) mixer.update(clock.getDelta());
+
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_materials_basic.ts b/examples-testing/examples/webgpu_materials_basic.ts
new file mode 100644
index 000000000..0161a9c7b
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_basic.ts
@@ -0,0 +1,137 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+const spheres = [];
+
+let mouseX = 0;
+let mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+const params = {
+    color: '#ffffff',
+    mapping: THREE.CubeReflectionMapping,
+    refractionRatio: 0.98,
+    transparent: false,
+    opacity: 1,
+};
+
+const mappings = { ReflectionMapping: THREE.CubeReflectionMapping, RefractionMapping: THREE.CubeRefractionMapping };
+
+document.addEventListener('mousemove', onDocumentMouseMove);
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
+    camera.position.z = 3;
+
+    const path = './textures/cube/pisa/';
+    const format = '.png';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const textureCube = new THREE.CubeTextureLoader().load(urls);
+
+    scene = new THREE.Scene();
+    scene.background = textureCube;
+
+    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
+    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
+
+    for (let i = 0; i < 500; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = Math.random() * 10 - 5;
+        mesh.position.y = Math.random() * 10 - 5;
+        mesh.position.z = Math.random() * 10 - 5;
+
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
+
+        scene.add(mesh);
+
+        spheres.push(mesh);
+    }
+
+    //
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const gui = new GUI({ width: 300 });
+
+    gui.addColor(params, 'color').onChange(value => material.color.set(value));
+    gui.add(params, 'mapping', mappings).onChange(value => {
+        textureCube.mapping = value;
+        material.needsUpdate = true;
+    });
+    gui.add(params, 'refractionRatio')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(value => (material.refractionRatio = value));
+    gui.add(params, 'transparent').onChange(value => {
+        material.transparent = value;
+        material.needsUpdate = true;
+    });
+    gui.add(params, 'opacity')
+        .min(0.0)
+        .max(1.0)
+        .step(0.01)
+        .onChange(value => (material.opacity = value));
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = (event.clientX - windowHalfX) / 100;
+    mouseY = (event.clientY - windowHalfY) / 100;
+}
+
+//
+
+function animate() {
+    const timer = 0.0001 * Date.now();
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0, il = spheres.length; i < il; i++) {
+        const sphere = spheres[i];
+
+        sphere.position.x = 5 * Math.cos(timer + i);
+        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_materials_displacementmap.ts b/examples-testing/examples/webgpu_materials_displacementmap.ts
new file mode 100644
index 000000000..54d26d65e
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_displacementmap.ts
@@ -0,0 +1,224 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+
+let stats;
+let camera, scene, renderer, controls;
+
+const settings = {
+    metalness: 1.0,
+    roughness: 0.4,
+    ambientIntensity: 0.2,
+    aoMapIntensity: 1.0,
+    envMapIntensity: 1.0,
+    displacementScale: 2.436143, // from original model
+    normalScale: 1.0,
+};
+
+let mesh, material;
+
+let pointLight, ambientLight;
+
+const height = 500; // of camera frustum
+
+let r = 0.0;
+
+init();
+initGui();
+
+// Init gui
+function initGui() {
+    const gui = new GUI();
+
+    gui.add(settings, 'metalness')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.metalness = value;
+        });
+
+    gui.add(settings, 'roughness')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.roughness = value;
+        });
+
+    gui.add(settings, 'aoMapIntensity')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            material.aoMapIntensity = value;
+        });
+
+    gui.add(settings, 'ambientIntensity')
+        .min(0)
+        .max(1)
+        .onChange(function (value) {
+            ambientLight.intensity = value;
+        });
+
+    gui.add(settings, 'envMapIntensity')
+        .min(0)
+        .max(3)
+        .onChange(function (value) {
+            material.envMapIntensity = value;
+        });
+
+    gui.add(settings, 'displacementScale')
+        .min(0)
+        .max(3.0)
+        .onChange(function (value) {
+            material.displacementScale = value;
+        });
+
+    gui.add(settings, 'normalScale')
+        .min(-1)
+        .max(1)
+        .onChange(function (value) {
+            material.normalScale.set(1, -1).multiplyScalar(value);
+        });
+}
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setAnimationLoop(animate);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    scene = new THREE.Scene();
+
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera(-height * aspect, height * aspect, height, -height, 1, 10000);
+    camera.position.z = 1500;
+    scene.add(camera);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+    controls.enableDamping = true;
+
+    // lights
+
+    ambientLight = new THREE.AmbientLight(0xffffff, settings.ambientIntensity);
+    scene.add(ambientLight);
+
+    pointLight = new THREE.PointLight(0xff0000, 1.5, 0, 0);
+    pointLight.position.z = 2500;
+    scene.add(pointLight);
+
+    const pointLight2 = new THREE.PointLight(0xff6666, 3, 0, 0);
+    camera.add(pointLight2);
+
+    const pointLight3 = new THREE.PointLight(0x0000ff, 1.5, 0, 0);
+    pointLight3.position.x = -1000;
+    pointLight3.position.z = 1000;
+    scene.add(pointLight3);
+
+    // env map
+
+    const path = 'textures/cube/SwedishRoyalCastle/';
+    const format = '.jpg';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
+
+    // textures
+
+    const textureLoader = new THREE.TextureLoader();
+    const normalMap = textureLoader.load('models/obj/ninja/normal.png');
+    const aoMap = textureLoader.load('models/obj/ninja/ao.jpg');
+    const displacementMap = textureLoader.load('models/obj/ninja/displacement.jpg');
+
+    // material
+
+    material = new THREE.MeshStandardNodeMaterial({
+        color: 0xc1c1c1,
+        roughness: settings.roughness,
+        metalness: settings.metalness,
+
+        normalMap: normalMap,
+        normalScale: new THREE.Vector2(1, -1), // why does the normal map require negation in this case?
+
+        aoMap: aoMap,
+        aoMapIntensity: 1,
+
+        displacementMap: displacementMap,
+        displacementScale: settings.displacementScale,
+        displacementBias: -0.428408, // from original model
+
+        envMap: reflectionCube,
+        envMapIntensity: settings.envMapIntensity,
+
+        side: THREE.DoubleSide,
+    });
+
+    //
+
+    const loader = new OBJLoader();
+    loader.load('models/obj/ninja/ninjaHead_Low.obj', function (group) {
+        const geometry = group.children[0].geometry;
+        geometry.center();
+
+        mesh = new THREE.Mesh(geometry, material);
+        mesh.scale.multiplyScalar(25);
+        scene.add(mesh);
+    });
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera.left = -height * aspect;
+    camera.right = height * aspect;
+    camera.top = height;
+    camera.bottom = -height;
+
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    controls.update();
+
+    stats.begin();
+    render();
+    stats.end();
+}
+
+function render() {
+    pointLight.position.x = 2500 * Math.cos(r);
+    pointLight.position.z = 2500 * Math.sin(r);
+
+    r += 0.01;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_materials_envmaps.ts b/examples-testing/examples/webgpu_materials_envmaps.ts
new file mode 100644
index 000000000..f49b4ca1e
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_envmaps.ts
@@ -0,0 +1,107 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let controls, camera, scene, renderer;
+let textureEquirec, textureCube;
+let sphereMesh, sphereMaterial, params;
+
+init();
+
+function init() {
+    // CAMERAS
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 0, 2.5);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    // Textures
+
+    const loader = new THREE.CubeTextureLoader();
+    loader.setPath('textures/cube/Bridge2/');
+
+    textureCube = loader.load(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
+
+    const textureLoader = new THREE.TextureLoader();
+
+    textureEquirec = textureLoader.load('textures/2294472375_24a3b8ef46_o.jpg');
+    textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
+    textureEquirec.colorSpace = THREE.SRGBColorSpace;
+
+    scene.background = textureCube;
+
+    //
+
+    const geometry = new THREE.IcosahedronGeometry(1, 15);
+    sphereMaterial = new THREE.MeshBasicMaterial({ envMap: textureCube });
+    sphereMesh = new THREE.Mesh(geometry, sphereMaterial);
+    scene.add(sphereMesh);
+
+    //
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1.5;
+    controls.maxDistance = 6;
+
+    //
+
+    params = {
+        Cube: function () {
+            scene.background = textureCube;
+
+            sphereMaterial.envMap = textureCube;
+            sphereMaterial.needsUpdate = true;
+        },
+        Equirectangular: function () {
+            scene.background = textureEquirec;
+
+            sphereMaterial.envMap = textureEquirec;
+            sphereMaterial.needsUpdate = true;
+        },
+        Refraction: false,
+    };
+
+    const gui = new GUI({ width: 300 });
+    gui.add(params, 'Cube');
+    gui.add(params, 'Equirectangular');
+    gui.add(params, 'Refraction').onChange(function (value) {
+        if (value) {
+            textureEquirec.mapping = THREE.EquirectangularRefractionMapping;
+            textureCube.mapping = THREE.CubeRefractionMapping;
+        } else {
+            textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
+            textureCube.mapping = THREE.CubeReflectionMapping;
+        }
+
+        sphereMaterial.needsUpdate = true;
+    });
+    gui.open();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    camera.lookAt(scene.position);
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_materials_lightmap.ts b/examples-testing/examples/webgpu_materials_lightmap.ts
new file mode 100644
index 000000000..616645aab
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_lightmap.ts
@@ -0,0 +1,94 @@
+import * as THREE from 'three';
+import { vec4, color, positionLocal, mix } from 'three/tsl';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, stats;
+let camera, scene, renderer;
+
+init();
+
+async function init() {
+    const { innerWidth, innerHeight } = window;
+
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // CAMERA
+
+    camera = new THREE.PerspectiveCamera(40, innerWidth / innerHeight, 1, 10000);
+    camera.position.set(700, 200, -500);
+
+    // SCENE
+
+    scene = new THREE.Scene();
+
+    // LIGHTS
+
+    const light = new THREE.DirectionalLight(0xd5deff);
+    light.position.x = 300;
+    light.position.y = 250;
+    light.position.z = -500;
+    scene.add(light);
+
+    // SKYDOME
+
+    const topColor = new THREE.Color().copy(light.color);
+    const bottomColor = new THREE.Color(0xffffff);
+    const offset = 400;
+    const exponent = 0.6;
+
+    const h = positionLocal.add(offset).normalize().y;
+
+    const skyMat = new THREE.MeshBasicNodeMaterial();
+    skyMat.colorNode = vec4(mix(color(bottomColor), color(topColor), h.max(0.0).pow(exponent)), 1.0);
+    skyMat.side = THREE.BackSide;
+
+    const sky = new THREE.Mesh(new THREE.SphereGeometry(4000, 32, 15), skyMat);
+    scene.add(sky);
+
+    // MODEL
+
+    const loader = new THREE.ObjectLoader();
+    const object = await loader.loadAsync('models/json/lightmap/lightmap.json');
+    scene.add(object);
+
+    // RENDERER
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setAnimationLoop(animate);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(innerWidth, innerHeight);
+    container.appendChild(renderer.domElement);
+
+    // CONTROLS
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = (0.9 * Math.PI) / 2;
+    controls.enableZoom = false;
+
+    // STATS
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_materials_toon.ts b/examples-testing/examples/webgpu_materials_toon.ts
new file mode 100644
index 000000000..217460596
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_toon.ts
@@ -0,0 +1,148 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+let container, stats;
+
+let camera, scene, renderer;
+let particleLight;
+
+const loader = new FontLoader();
+loader.load('fonts/gentilis_regular.typeface.json', function (font) {
+    init(font);
+});
+
+function init(font) {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2500);
+    camera.position.set(0.0, 400, 400 * 3.5);
+
+    //
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x444488);
+
+    //
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    container.appendChild(renderer.domElement);
+
+    // Materials
+
+    const cubeWidth = 400;
+    const numberOfSphersPerSide = 5;
+    const sphereRadius = (cubeWidth / numberOfSphersPerSide) * 0.8 * 0.5;
+    const stepSize = 1.0 / numberOfSphersPerSide;
+
+    const geometry = new THREE.SphereGeometry(sphereRadius, 32, 16);
+
+    for (let alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex++) {
+        const colors = new Uint8Array(alphaIndex + 2);
+
+        for (let c = 0; c <= colors.length; c++) {
+            colors[c] = (c / colors.length) * 256;
+        }
+
+        const gradientMap = new THREE.DataTexture(colors, colors.length, 1, THREE.RedFormat);
+        gradientMap.needsUpdate = true;
+
+        for (let beta = 0; beta <= 1.0; beta += stepSize) {
+            for (let gamma = 0; gamma <= 1.0; gamma += stepSize) {
+                // basic monochromatic energy preservation
+                const diffuseColor = new THREE.Color()
+                    .setHSL(alpha, 0.5, gamma * 0.5 + 0.1)
+                    .multiplyScalar(1 - beta * 0.2);
+
+                const material = new THREE.MeshToonNodeMaterial({
+                    color: diffuseColor,
+                    gradientMap: gradientMap,
+                });
+
+                const mesh = new THREE.Mesh(geometry, material);
+
+                mesh.position.x = alpha * 400 - 200;
+                mesh.position.y = beta * 400 - 200;
+                mesh.position.z = gamma * 400 - 200;
+
+                scene.add(mesh);
+            }
+        }
+    }
+
+    function addLabel(name, location) {
+        const textGeo = new TextGeometry(name, {
+            font: font,
+
+            size: 20,
+            depth: 1,
+            curveSegments: 1,
+        });
+
+        const textMaterial = new THREE.MeshBasicNodeMaterial();
+        const textMesh = new THREE.Mesh(textGeo, textMaterial);
+        textMesh.position.copy(location);
+        scene.add(textMesh);
+    }
+
+    addLabel('-gradientMap', new THREE.Vector3(-350, 0, 0));
+    addLabel('+gradientMap', new THREE.Vector3(350, 0, 0));
+
+    addLabel('-diffuse', new THREE.Vector3(0, 0, -300));
+    addLabel('+diffuse', new THREE.Vector3(0, 0, 300));
+
+    particleLight = new THREE.Mesh(
+        new THREE.SphereGeometry(4, 8, 8),
+        new THREE.MeshBasicNodeMaterial({ color: 0xffffff }),
+    );
+    scene.add(particleLight);
+
+    // Lights
+
+    scene.add(new THREE.AmbientLight(0xc1c1c1, 3));
+
+    const pointLight = new THREE.PointLight(0xffffff, 2, 800, 0);
+    particleLight.add(pointLight);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 200;
+    controls.maxDistance = 2000;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    const timer = Date.now() * 0.00025;
+
+    particleLight.position.x = Math.sin(timer * 7) * 300;
+    particleLight.position.y = Math.cos(timer * 5) * 400;
+    particleLight.position.z = Math.cos(timer * 3) * 300;
+
+    stats.begin();
+
+    renderer.render(scene, camera);
+
+    stats.end();
+}
diff --git a/examples-testing/examples/webgpu_materials_transmission.ts b/examples-testing/examples/webgpu_materials_transmission.ts
new file mode 100644
index 000000000..0e04ddad9
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_transmission.ts
@@ -0,0 +1,168 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+const params = {
+    color: 0xffffff,
+    transmission: 1,
+    opacity: 1,
+    metalness: 0,
+    roughness: 0,
+    ior: 1.5,
+    thickness: 0.01,
+    specularIntensity: 1,
+    specularColor: 0xffffff,
+    envMapIntensity: 1,
+    lightIntensity: 1,
+    exposure: 1,
+};
+
+let camera, scene, renderer;
+
+let mesh;
+
+const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
+    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
+
+    init();
+    render();
+});
+
+function init() {
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    document.body.appendChild(renderer.domElement);
+
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = params.exposure;
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.position.set(0, 0, 120);
+
+    //
+
+    scene.background = hdrEquirect;
+
+    //
+
+    const geometry = new THREE.SphereGeometry(20, 64, 32);
+
+    const texture = new THREE.CanvasTexture(generateTexture());
+    texture.magFilter = THREE.NearestFilter;
+    texture.wrapT = THREE.RepeatWrapping;
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.repeat.set(1, 3.5);
+
+    const material = new THREE.MeshPhysicalMaterial({
+        color: params.color,
+        metalness: params.metalness,
+        roughness: params.roughness,
+        ior: params.ior,
+        alphaMap: texture,
+        envMap: hdrEquirect,
+        envMapIntensity: params.envMapIntensity,
+        transmission: params.transmission, // use material.transmission for glass materials
+        specularIntensity: params.specularIntensity,
+        specularColor: params.specularColor,
+        opacity: params.opacity,
+        side: THREE.DoubleSide,
+        transparent: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 10;
+    controls.maxDistance = 150;
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    gui.addColor(params, 'color').onChange(function () {
+        material.color.set(params.color);
+    });
+
+    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
+        material.transmission = params.transmission;
+    });
+
+    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
+        material.opacity = params.opacity;
+    });
+
+    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
+        material.metalness = params.metalness;
+    });
+
+    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
+        material.roughness = params.roughness;
+    });
+
+    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
+        material.ior = params.ior;
+    });
+
+    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
+        material.thickness = params.thickness;
+    });
+
+    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
+        material.specularIntensity = params.specularIntensity;
+    });
+
+    gui.addColor(params, 'specularColor').onChange(function () {
+        material.specularColor.set(params.specularColor);
+    });
+
+    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
+        .name('envMap intensity')
+        .onChange(function () {
+            material.envMapIntensity = params.envMapIntensity;
+        });
+
+    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
+        renderer.toneMappingExposure = params.exposure;
+    });
+
+    gui.open();
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+//
+
+function generateTexture() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 2;
+    canvas.height = 2;
+
+    const context = canvas.getContext('2d');
+    context.fillStyle = 'white';
+    context.fillRect(0, 1, 2, 1);
+
+    return canvas;
+}
+
+function render() {
+    renderer.renderAsync(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_materials_video.ts b/examples-testing/examples/webgpu_materials_video.ts
new file mode 100644
index 000000000..bd84aba0f
--- /dev/null
+++ b/examples-testing/examples/webgpu_materials_video.ts
@@ -0,0 +1,184 @@
+import * as THREE from 'three';
+
+let container;
+
+let camera, scene, renderer;
+
+let video, texture, material, mesh;
+
+let mouseX = 0;
+let mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+let cube_count;
+
+const meshes = [],
+    materials = [],
+    xgrid = 20,
+    ygrid = 10;
+
+const startButton = document.getElementById('startButton');
+startButton.addEventListener('click', function () {
+    init();
+});
+
+function init() {
+    const overlay = document.getElementById('overlay');
+    overlay.remove();
+
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
+    camera.position.z = 500;
+
+    scene = new THREE.Scene();
+
+    const light = new THREE.DirectionalLight(0xffffff, 7);
+    light.position.set(0.5, 1, 1).normalize();
+    scene.add(light);
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    container.appendChild(renderer.domElement);
+
+    video = document.getElementById('video');
+    video.play();
+    video.addEventListener('play', function () {
+        this.currentTime = 3;
+    });
+
+    texture = new THREE.VideoTexture(video);
+
+    //
+
+    let i, j, ox, oy, geometry;
+
+    const ux = 1 / xgrid;
+    const uy = 1 / ygrid;
+
+    const xsize = 480 / xgrid;
+    const ysize = 204 / ygrid;
+
+    const parameters = { color: 0xffffff, map: texture };
+
+    cube_count = 0;
+
+    for (i = 0; i < xgrid; i++) {
+        for (j = 0; j < ygrid; j++) {
+            ox = i;
+            oy = j;
+
+            geometry = new THREE.BoxGeometry(xsize, ysize, xsize);
+
+            change_uvs(geometry, ux, uy, ox, oy);
+
+            materials[cube_count] = new THREE.MeshPhongMaterial(parameters);
+
+            material = materials[cube_count];
+
+            material.hue = i / xgrid;
+            material.saturation = 1 - j / ygrid;
+
+            material.color.setHSL(material.hue, material.saturation, 0.5);
+
+            mesh = new THREE.Mesh(geometry, material);
+
+            mesh.position.x = (i - xgrid / 2) * xsize;
+            mesh.position.y = (j - ygrid / 2) * ysize;
+            mesh.position.z = 0;
+
+            mesh.scale.x = mesh.scale.y = mesh.scale.z = 1;
+
+            scene.add(mesh);
+
+            mesh.dx = 0.001 * (0.5 - Math.random());
+            mesh.dy = 0.001 * (0.5 - Math.random());
+
+            meshes[cube_count] = mesh;
+
+            cube_count += 1;
+        }
+    }
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function change_uvs(geometry, unitx, unity, offsetx, offsety) {
+    const uvs = geometry.attributes.uv.array;
+
+    for (let i = 0; i < uvs.length; i += 2) {
+        uvs[i] = (uvs[i] + offsetx) * unitx;
+        uvs[i + 1] = (uvs[i + 1] + offsety) * unity;
+    }
+}
+
+function onDocumentMouseMove(event) {
+    mouseX = event.clientX - windowHalfX;
+    mouseY = (event.clientY - windowHalfY) * 0.3;
+}
+
+//
+
+let h,
+    counter = 1;
+
+function render() {
+    const time = Date.now() * 0.00005;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y += (-mouseY - camera.position.y) * 0.05;
+
+    camera.lookAt(scene.position);
+
+    for (let i = 0; i < cube_count; i++) {
+        material = materials[i];
+
+        h = ((360 * (material.hue + time)) % 360) / 360;
+        material.color.setHSL(h, material.saturation, 0.5);
+    }
+
+    if (counter % 1000 > 200) {
+        for (let i = 0; i < cube_count; i++) {
+            mesh = meshes[i];
+
+            mesh.rotation.x += 10 * mesh.dx;
+            mesh.rotation.y += 10 * mesh.dy;
+
+            mesh.position.x -= 150 * mesh.dx;
+            mesh.position.y += 150 * mesh.dy;
+            mesh.position.z += 300 * mesh.dx;
+        }
+    }
+
+    if (counter % 1000 === 0) {
+        for (let i = 0; i < cube_count; i++) {
+            mesh = meshes[i];
+
+            mesh.dx *= -1;
+            mesh.dy *= -1;
+        }
+    }
+
+    counter++;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_mesh_batch.ts b/examples-testing/examples/webgpu_mesh_batch.ts
new file mode 100644
index 000000000..48c6fad63
--- /dev/null
+++ b/examples-testing/examples/webgpu_mesh_batch.ts
@@ -0,0 +1,269 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { radixSort } from 'three/addons/utils/SortUtils.js';
+
+import { transformedNormalView, directionToColor, diffuseColor } from 'three/tsl';
+
+let camera, scene, renderer;
+let controls, stats;
+let gui;
+let geometries, mesh, material;
+const ids = [];
+
+const matrix = new THREE.Matrix4();
+
+//
+
+const position = new THREE.Vector3();
+const rotation = new THREE.Euler();
+const quaternion = new THREE.Quaternion();
+const scale = new THREE.Vector3();
+
+//
+
+const MAX_GEOMETRY_COUNT = 20000;
+
+const api = {
+    webgpu: true,
+    count: 512,
+    dynamic: 16,
+
+    sortObjects: true,
+    perObjectFrustumCulled: true,
+    opacity: 1,
+    useCustomSort: true,
+};
+
+init();
+
+//
+
+function randomizeMatrix(matrix) {
+    position.x = Math.random() * 40 - 20;
+    position.y = Math.random() * 40 - 20;
+    position.z = Math.random() * 40 - 20;
+
+    rotation.x = Math.random() * 2 * Math.PI;
+    rotation.y = Math.random() * 2 * Math.PI;
+    rotation.z = Math.random() * 2 * Math.PI;
+
+    quaternion.setFromEuler(rotation);
+
+    scale.x = scale.y = scale.z = 0.5 + Math.random() * 0.5;
+
+    return matrix.compose(position, quaternion, scale);
+}
+
+function randomizeRotationSpeed(rotation) {
+    rotation.x = Math.random() * 0.01;
+    rotation.y = Math.random() * 0.01;
+    rotation.z = Math.random() * 0.01;
+    return rotation;
+}
+
+function initGeometries() {
+    geometries = [
+        new THREE.ConeGeometry(1.0, 2.0),
+        new THREE.BoxGeometry(2.0, 2.0, 2.0),
+        new THREE.SphereGeometry(1.0, 16, 8),
+    ];
+}
+
+function createMaterial() {
+    if (!material) {
+        material = new THREE.MeshBasicNodeMaterial();
+        material.outputNode = diffuseColor.mul(directionToColor(transformedNormalView).y.add(0.5));
+    }
+
+    return material;
+}
+
+function cleanup() {
+    if (mesh) {
+        mesh.parent.remove(mesh);
+
+        if (mesh.dispose) {
+            mesh.dispose();
+        }
+    }
+}
+
+function initMesh() {
+    cleanup();
+    initBatchedMesh();
+}
+
+function initBatchedMesh() {
+    const geometryCount = api.count;
+    const vertexCount = geometries.length * 512;
+    const indexCount = geometries.length * 1024;
+
+    const euler = new THREE.Euler();
+    const matrix = new THREE.Matrix4();
+    mesh = new THREE.BatchedMesh(geometryCount, vertexCount, indexCount, createMaterial());
+    mesh.userData.rotationSpeeds = [];
+
+    // disable full-object frustum culling since all of the objects can be dynamic.
+    mesh.frustumCulled = false;
+
+    ids.length = 0;
+
+    const geometryIds = [
+        mesh.addGeometry(geometries[0]),
+        mesh.addGeometry(geometries[1]),
+        mesh.addGeometry(geometries[2]),
+    ];
+    for (let i = 0; i < api.count; i++) {
+        const id = mesh.addInstance(geometryIds[i % geometryIds.length]);
+        mesh.setMatrixAt(id, randomizeMatrix(matrix));
+        mesh.setColorAt(id, new THREE.Color(Math.random() * 0xffffff));
+
+        const rotationMatrix = new THREE.Matrix4();
+        rotationMatrix.makeRotationFromEuler(randomizeRotationSpeed(euler));
+        mesh.userData.rotationSpeeds.push(rotationMatrix);
+
+        ids.push(id);
+    }
+
+    scene.add(mesh);
+}
+
+function init(forceWebGL = false) {
+    if (renderer) {
+        renderer.dispose();
+        controls.dispose();
+        document.body.removeChild(stats.dom);
+        document.body.removeChild(renderer.domElement);
+    }
+
+    // camera
+
+    const aspect = window.innerWidth / window.innerHeight;
+
+    camera = new THREE.PerspectiveCamera(70, aspect, 1, 100);
+    camera.position.z = 30;
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    renderer.setAnimationLoop(animate);
+
+    // scene
+
+    scene = new THREE.Scene();
+    scene.background = forceWebGL ? new THREE.Color(0xffc1c1) : new THREE.Color(0xc1c1ff);
+
+    document.body.appendChild(renderer.domElement);
+
+    initGeometries();
+    initMesh();
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = 1.0;
+
+    // stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // gui
+
+    gui = new GUI();
+    gui.add(api, 'webgpu').onChange(() => {
+        init(!api.webgpu);
+    });
+    gui.add(api, 'count', 1, MAX_GEOMETRY_COUNT).step(1).onChange(initMesh);
+    gui.add(api, 'dynamic', 0, MAX_GEOMETRY_COUNT).step(1);
+
+    gui.add(api, 'opacity', 0, 1).onChange(v => {
+        if (v < 1) {
+            material.transparent = true;
+            material.depthWrite = false;
+        } else {
+            material.transparent = false;
+            material.depthWrite = true;
+        }
+
+        material.opacity = v;
+        material.needsUpdate = true;
+    });
+    gui.add(api, 'sortObjects');
+    gui.add(api, 'perObjectFrustumCulled');
+    gui.add(api, 'useCustomSort');
+
+    // listeners
+
+    window.addEventListener('resize', onWindowResize);
+
+    function onWindowResize() {
+        const width = window.innerWidth;
+        const height = window.innerHeight;
+
+        camera.aspect = width / height;
+        camera.updateProjectionMatrix();
+
+        renderer.setSize(width, height);
+    }
+
+    async function animate() {
+        animateMeshes();
+
+        controls.update();
+
+        if (mesh.isBatchedMesh) {
+            mesh.sortObjects = api.sortObjects;
+            mesh.perObjectFrustumCulled = api.perObjectFrustumCulled;
+            mesh.setCustomSort(api.useCustomSort ? sortFunction : null);
+        }
+
+        await renderer.renderAsync(scene, camera);
+
+        stats.update();
+    }
+
+    function animateMeshes() {
+        const loopNum = Math.min(api.count, api.dynamic);
+
+        for (let i = 0; i < loopNum; i++) {
+            const rotationMatrix = mesh.userData.rotationSpeeds[i];
+            const id = ids[i];
+
+            mesh.getMatrixAt(id, matrix);
+            matrix.multiply(rotationMatrix);
+            mesh.setMatrixAt(id, matrix);
+        }
+    }
+}
+
+//
+
+function sortFunction(list, camera) {
+    // initialize options
+    this._options = this._options || {
+        get: el => el.z,
+        aux: new Array(this.maxInstanceCount),
+    };
+
+    const options = this._options;
+    options.reversed = this.material.transparent;
+
+    // convert depth to unsigned 32 bit range
+    const factor = (2 ** 32 - 1) / camera.far; // UINT32_MAX / max_depth
+    for (let i = 0, l = list.length; i < l; i++) {
+        list[i].z *= factor;
+    }
+
+    // perform a fast-sort using the hybrid radix sort function
+    radixSort(list, options);
+}
diff --git a/examples-testing/examples/webgpu_morphtargets.ts b/examples-testing/examples/webgpu_morphtargets.ts
new file mode 100644
index 000000000..9fb7075cb
--- /dev/null
+++ b/examples-testing/examples/webgpu_morphtargets.ts
@@ -0,0 +1,121 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let container, camera, scene, renderer, mesh;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x8fbcd4);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
+    camera.position.z = 10;
+    scene.add(camera);
+
+    scene.add(new THREE.AmbientLight(0x8fbcd4, 1.5));
+
+    const pointLight = new THREE.PointLight(0xffffff, 200);
+    camera.add(pointLight);
+
+    const geometry = createGeometry();
+
+    const material = new THREE.MeshPhongMaterial({
+        color: 0xff0000,
+        flatShading: true,
+    });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    initGUI();
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(function () {
+        renderer.render(scene, camera);
+    });
+    container.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createGeometry() {
+    const geometry = new THREE.BoxGeometry(2, 2, 2, 32, 32, 32);
+
+    // create an empty array to hold targets for the attribute we want to morph
+    // morphing positions and normals is supported
+    geometry.morphAttributes.position = [];
+
+    // the original positions of the cube's vertices
+    const positionAttribute = geometry.attributes.position;
+
+    // for the first morph target we'll move the cube's vertices onto the surface of a sphere
+    const spherePositions = [];
+
+    // for the second morph target, we'll twist the cubes vertices
+    const twistPositions = [];
+    const direction = new THREE.Vector3(1, 0, 0);
+    const vertex = new THREE.Vector3();
+
+    for (let i = 0; i < positionAttribute.count; i++) {
+        const x = positionAttribute.getX(i);
+        const y = positionAttribute.getY(i);
+        const z = positionAttribute.getZ(i);
+
+        spherePositions.push(
+            x * Math.sqrt(1 - (y * y) / 2 - (z * z) / 2 + (y * y * z * z) / 3),
+            y * Math.sqrt(1 - (z * z) / 2 - (x * x) / 2 + (z * z * x * x) / 3),
+            z * Math.sqrt(1 - (x * x) / 2 - (y * y) / 2 + (x * x * y * y) / 3),
+        );
+
+        // stretch along the x-axis so we can see the twist better
+        vertex.set(x * 2, y, z);
+
+        vertex.applyAxisAngle(direction, (Math.PI * x) / 2).toArray(twistPositions, twistPositions.length);
+    }
+
+    // add the spherical positions as the first morph target
+    geometry.morphAttributes.position[0] = new THREE.Float32BufferAttribute(spherePositions, 3);
+
+    // add the twisted positions as the second morph target
+    geometry.morphAttributes.position[1] = new THREE.Float32BufferAttribute(twistPositions, 3);
+
+    return geometry;
+}
+
+function initGUI() {
+    // Set up dat.GUI to control targets
+    const params = {
+        Spherify: 0,
+        Twist: 0,
+    };
+    const gui = new GUI({ title: 'Morph Targets' });
+
+    gui.add(params, 'Spherify', 0, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            mesh.morphTargetInfluences[0] = value;
+        });
+    gui.add(params, 'Twist', 0, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            mesh.morphTargetInfluences[1] = value;
+        });
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
diff --git a/examples-testing/examples/webgpu_morphtargets_face.ts b/examples-testing/examples/webgpu_morphtargets_face.ts
new file mode 100644
index 000000000..ea9f86588
--- /dev/null
+++ b/examples-testing/examples/webgpu_morphtargets_face.ts
@@ -0,0 +1,102 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+init();
+
+async function init() {
+    let mixer;
+
+    const clock = new THREE.Clock();
+
+    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
+    camera.position.set(-1.8, 0.8, 3);
+
+    const scene = new THREE.Scene();
+
+    const renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    await renderer.init();
+
+    const environment = new RoomEnvironment();
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+
+    scene.background = new THREE.Color(0x666666);
+    scene.environment = pmremGenerator.fromScene(environment).texture;
+
+    const ktx2Loader = await new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupportAsync(renderer);
+
+    new GLTFLoader()
+        .setKTX2Loader(ktx2Loader)
+        .setMeshoptDecoder(MeshoptDecoder)
+        .load('models/gltf/facecap.glb', gltf => {
+            const mesh = gltf.scene.children[0];
+
+            scene.add(mesh);
+
+            mixer = new THREE.AnimationMixer(mesh);
+
+            mixer.clipAction(gltf.animations[0]).play();
+
+            // GUI
+
+            const head = mesh.getObjectByName('mesh_2');
+            const influences = head.morphTargetInfluences;
+
+            const gui = new GUI();
+            gui.close();
+
+            for (const [key, value] of Object.entries(head.morphTargetDictionary)) {
+                gui.add(influences, value, 0, 1, 0.01).name(key.replace('blendShape1.', '')).listen();
+            }
+        });
+
+    scene.background = new THREE.Color(0x666666);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 2.5;
+    controls.maxDistance = 5;
+    controls.minAzimuthAngle = -Math.PI / 2;
+    controls.maxAzimuthAngle = Math.PI / 2;
+    controls.maxPolarAngle = Math.PI / 1.8;
+    controls.target.set(0, 0.15, -0.2);
+
+    const stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    function animate() {
+        const delta = clock.getDelta();
+
+        if (mixer) {
+            mixer.update(delta);
+        }
+
+        renderer.render(scene, camera);
+
+        controls.update();
+
+        stats.update();
+    }
+
+    window.addEventListener('resize', () => {
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+
+        renderer.setSize(window.innerWidth, window.innerHeight);
+    });
+}
diff --git a/examples-testing/examples/webgpu_mrt.ts b/examples-testing/examples/webgpu_mrt.ts
new file mode 100644
index 000000000..3737c463e
--- /dev/null
+++ b/examples-testing/examples/webgpu_mrt.ts
@@ -0,0 +1,120 @@
+import * as THREE from 'three';
+import {
+    output,
+    transformedNormalView,
+    pass,
+    step,
+    diffuseColor,
+    emissive,
+    directionToColor,
+    viewportUV,
+    mix,
+    mrt,
+    Fn,
+} from 'three/tsl';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer;
+let postProcessing;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    // scene
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', function (gltf) {
+            scene.add(gltf.scene);
+        });
+    });
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    // post processing
+
+    const scenePass = pass(scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter });
+    scenePass.setMRT(
+        mrt({
+            output: output,
+            normal: directionToColor(transformedNormalView),
+            diffuse: diffuseColor,
+            emissive: emissive,
+        }),
+    );
+
+    // optimize textures
+
+    const normalTexture = scenePass.getTexture('normal');
+    const diffuseTexture = scenePass.getTexture('diffuse');
+    const emissiveTexture = scenePass.getTexture('emissive');
+
+    normalTexture.type = diffuseTexture.type = emissiveTexture.type = THREE.UnsignedByteType;
+
+    // post processing - mrt
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    postProcessing.outputColorTransform = false;
+    postProcessing.outputNode = Fn(() => {
+        const output = scenePass.getTextureNode('output'); // output name is optional here
+        const normal = scenePass.getTextureNode('normal');
+        const diffuse = scenePass.getTextureNode('diffuse');
+        const emissive = scenePass.getTextureNode('emissive');
+
+        const out = mix(output.renderOutput(), output, step(0.2, viewportUV.x));
+        const nor = mix(out, normal, step(0.4, viewportUV.x));
+        const emi = mix(nor, emissive, step(0.6, viewportUV.x));
+        const dif = mix(emi, diffuse, step(0.8, viewportUV.x));
+
+        return dif;
+    })();
+
+    // controls
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts b/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
new file mode 100644
index 000000000..ace1533a0
--- /dev/null
+++ b/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
@@ -0,0 +1,156 @@
+import * as THREE from 'three';
+import { mix, step, texture, viewportUV, mrt, output, transformedNormalWorld, uv, vec2 } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, torus;
+let quadMesh, sceneMRT, renderTarget, readbackTarget, material, readbackMaterial, pixelBuffer, pixelBufferTexture;
+
+const gui = new GUI();
+
+const options = {
+    selection: 'mrt',
+};
+
+gui.add(options, 'selection', ['mrt', 'diffuse', 'normal']);
+
+init();
+
+function init() {
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    document.body.appendChild(renderer.domElement);
+
+    // Create a multi render target with Float buffers
+
+    renderTarget = new THREE.RenderTarget(
+        window.innerWidth * window.devicePixelRatio,
+        window.innerHeight * window.devicePixelRatio,
+        { count: 2, minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter },
+    );
+
+    // Name our G-Buffer attachments for debugging
+
+    renderTarget.textures[0].name = 'output';
+    renderTarget.textures[1].name = 'normal';
+
+    // Init readback render target, readback data texture, readback material
+    // Be careful with the size! 512 is already big. Reading data back from the GPU is computationally intensive
+
+    const size = 512;
+
+    readbackTarget = new THREE.RenderTarget(size, size, { count: 2 });
+
+    pixelBuffer = new Uint8Array(size ** 2 * 4).fill(0);
+    pixelBufferTexture = new THREE.DataTexture(pixelBuffer, size, size);
+    pixelBufferTexture.type = THREE.UnsignedByteType;
+    pixelBufferTexture.format = THREE.RGBAFormat;
+
+    readbackMaterial = new THREE.MeshBasicNodeMaterial();
+    readbackMaterial.colorNode = texture(pixelBufferTexture);
+
+    // MRT
+
+    sceneMRT = mrt({
+        output: output,
+        normal: transformedNormalWorld,
+    });
+
+    // Scene
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x222222);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 50);
+    camera.position.z = 4;
+
+    const loader = new THREE.TextureLoader();
+
+    const diffuse = loader.load('textures/hardwood2_diffuse.jpg');
+    diffuse.colorSpace = THREE.SRGBColorSpace;
+    diffuse.wrapS = THREE.RepeatWrapping;
+    diffuse.wrapT = THREE.RepeatWrapping;
+
+    const torusMaterial = new THREE.NodeMaterial();
+    torusMaterial.colorNode = texture(diffuse, uv().mul(vec2(10, 4)));
+
+    torus = new THREE.Mesh(new THREE.TorusKnotGeometry(1, 0.3, 128, 32), torusMaterial);
+    scene.add(torus);
+
+    // Output
+
+    material = new THREE.NodeMaterial();
+    material.colorNode = mix(
+        texture(renderTarget.textures[0]),
+        texture(renderTarget.textures[1]),
+        step(0.5, viewportUV.x),
+    );
+
+    quadMesh = new THREE.QuadMesh(material);
+
+    // Controls
+
+    new OrbitControls(camera, renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    const dpr = renderer.getPixelRatio();
+    renderTarget.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
+}
+
+async function render(time) {
+    const selection = options.selection;
+
+    torus.rotation.y = (time / 1000) * 0.4;
+
+    const isMRT = selection === 'mrt';
+
+    // render scene into target
+    renderer.setMRT(isMRT ? sceneMRT : null);
+    renderer.setRenderTarget(isMRT ? renderTarget : readbackTarget);
+    renderer.render(scene, camera);
+
+    // render post FX
+    renderer.setMRT(null);
+    renderer.setRenderTarget(null);
+
+    if (isMRT) {
+        quadMesh.material = material;
+    } else {
+        quadMesh.material = readbackMaterial;
+
+        await readback();
+    }
+
+    quadMesh.render(renderer);
+}
+
+async function readback() {
+    const width = readbackTarget.width;
+    const height = readbackTarget.height;
+
+    const selection = options.selection;
+
+    if (selection === 'diffuse') {
+        pixelBuffer = await renderer.readRenderTargetPixelsAsync(readbackTarget, 0, 0, width, height, 0); // zero is optional
+
+        pixelBufferTexture.image.data = pixelBuffer;
+        pixelBufferTexture.needsUpdate = true;
+    } else if (selection === 'normal') {
+        pixelBuffer = await renderer.readRenderTargetPixelsAsync(readbackTarget, 0, 0, width, height, 1);
+
+        pixelBufferTexture.image.data = pixelBuffer;
+        pixelBufferTexture.needsUpdate = true;
+    }
+}
diff --git a/examples-testing/examples/webgpu_ocean.ts b/examples-testing/examples/webgpu_ocean.ts
new file mode 100644
index 000000000..9eb9922dd
--- /dev/null
+++ b/examples-testing/examples/webgpu_ocean.ts
@@ -0,0 +1,161 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { WaterMesh } from 'three/addons/objects/WaterMesh.js';
+import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
+
+let container, stats;
+let camera, scene, renderer;
+let controls, water, sun, mesh;
+
+init();
+
+function init() {
+    container = document.getElementById('container');
+
+    //
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 0.5;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
+    camera.position.set(30, 30, 100);
+
+    //
+
+    sun = new THREE.Vector3();
+
+    // Water
+
+    const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
+    const loader = new THREE.TextureLoader();
+    const waterNormals = loader.load('textures/waternormals.jpg');
+    waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
+
+    water = new WaterMesh(waterGeometry, {
+        waterNormals: waterNormals,
+        sunDirection: new THREE.Vector3(),
+        sunColor: 0xffffff,
+        waterColor: 0x001e0f,
+        distortionScale: 3.7,
+    });
+
+    water.rotation.x = -Math.PI / 2;
+
+    scene.add(water);
+
+    // Skybox
+
+    const sky = new SkyMesh();
+    sky.scale.setScalar(10000);
+    scene.add(sky);
+
+    sky.turbidity.value = 10;
+    sky.rayleigh.value = 2;
+    sky.mieCoefficient.value = 0.005;
+    sky.mieDirectionalG.value = 0.8;
+
+    const parameters = {
+        elevation: 2,
+        azimuth: 180,
+    };
+
+    const pmremGenerator = new THREE.PMREMGenerator(renderer);
+    const sceneEnv = new THREE.Scene();
+
+    let renderTarget;
+
+    function updateSun() {
+        const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
+        const theta = THREE.MathUtils.degToRad(parameters.azimuth);
+
+        sun.setFromSphericalCoords(1, phi, theta);
+
+        sky.sunPosition.value.copy(sun);
+        water.sunDirection.value.copy(sun).normalize();
+
+        if (renderTarget !== undefined) renderTarget.dispose();
+
+        sceneEnv.add(sky);
+        renderTarget = pmremGenerator.fromScene(sceneEnv);
+        scene.add(sky);
+
+        scene.environment = renderTarget.texture;
+    }
+
+    renderer.init().then(updateSun);
+
+    //
+
+    const geometry = new THREE.BoxGeometry(30, 30, 30);
+    const material = new THREE.MeshStandardMaterial({ roughness: 0 });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI * 0.495;
+    controls.target.set(0, 10, 0);
+    controls.minDistance = 40.0;
+    controls.maxDistance = 200.0;
+    controls.update();
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    // GUI
+
+    const gui = new GUI();
+
+    const folderSky = gui.addFolder('Sky');
+    folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun);
+    folderSky.add(parameters, 'azimuth', -180, 180, 0.1).onChange(updateSun);
+    folderSky.open();
+
+    const folderWater = gui.addFolder('Water');
+    folderWater.add(water.distortionScale, 'value', 0, 8, 0.1).name('distortionScale');
+    folderWater.add(water.size, 'value', 0.1, 10, 0.1).name('size');
+    folderWater.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const time = performance.now() * 0.001;
+
+    mesh.position.y = Math.sin(time) * 20 + 5;
+    mesh.rotation.x = time * 0.5;
+    mesh.rotation.z = time * 0.51;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_parallax_uv.ts b/examples-testing/examples/webgpu_parallax_uv.ts
new file mode 100644
index 000000000..775399bfb
--- /dev/null
+++ b/examples-testing/examples/webgpu_parallax_uv.ts
@@ -0,0 +1,112 @@
+import * as THREE from 'three';
+import { texture, parallaxUV, overlay, uv } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+let controls;
+
+init();
+
+async function init() {
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 50);
+    camera.position.set(10, 14, 10);
+
+    // environment
+
+    const environmentTexture = await new THREE.CubeTextureLoader()
+        .setPath('./textures/cube/Park2/')
+        .loadAsync(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
+
+    scene.environment = environmentTexture;
+    scene.background = environmentTexture;
+
+    // textures
+
+    const loader = new THREE.TextureLoader();
+
+    const topTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Color.jpg');
+    topTexture.colorSpace = THREE.SRGBColorSpace;
+
+    const roughnessTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Roughness.jpg');
+    roughnessTexture.colorSpace = THREE.NoColorSpace;
+
+    const normalTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_NormalGL.jpg');
+    normalTexture.colorSpace = THREE.NoColorSpace;
+
+    const displaceTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Displacement.jpg');
+    displaceTexture.colorSpace = THREE.NoColorSpace;
+
+    //
+
+    const bottomTexture = await loader.loadAsync('textures/ambientcg/Ice003_1K-JPG_Color.jpg');
+    bottomTexture.colorSpace = THREE.SRGBColorSpace;
+    bottomTexture.wrapS = THREE.RepeatWrapping;
+    bottomTexture.wrapT = THREE.RepeatWrapping;
+
+    // paralax effect
+
+    const parallaxScale = 0.3;
+    const offsetUV = texture(displaceTexture).mul(parallaxScale);
+
+    const parallaxUVOffset = parallaxUV(uv(), offsetUV);
+    const parallaxResult = texture(bottomTexture, parallaxUVOffset);
+
+    const iceNode = overlay(texture(topTexture), parallaxResult);
+
+    // material
+
+    const material = new THREE.MeshStandardNodeMaterial();
+    material.colorNode = iceNode.mul(5); // increase the color intensity to 5 ( contrast )
+    material.roughnessNode = texture(roughnessTexture);
+    material.normalMap = normalTexture;
+    material.metalness = 0;
+
+    const geometry = new THREE.BoxGeometry(10, 10, 10);
+
+    const ground = new THREE.Mesh(geometry, material);
+    ground.rotateX(-Math.PI / 2);
+    scene.add(ground);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    renderer.toneMappingExposure = 6;
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0, 0);
+    controls.maxDistance = 40;
+    controls.minDistance = 10;
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = -1;
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_performance.ts b/examples-testing/examples/webgpu_performance.ts
new file mode 100644
index 000000000..315eba252
--- /dev/null
+++ b/examples-testing/examples/webgpu_performance.ts
@@ -0,0 +1,84 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+let camera, scene, renderer, stats;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(60, 60, 60);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+
+    renderer.setAnimationLoop(render);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.environment = texture;
+
+        // model
+
+        const dracoLoader = new DRACOLoader();
+        dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
+
+        const loader = new GLTFLoader().setPath('models/gltf/');
+        loader.setDRACOLoader(dracoLoader);
+
+        loader.load('dungeon_warkarma.glb', async function (gltf) {
+            const model = gltf.scene;
+
+            // wait until the model can be added to the scene without blocking due to shader compilation
+
+            await renderer.compileAsync(model, camera, scene);
+
+            scene.add(model);
+        });
+    });
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 60;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    renderer.renderAsync(scene, camera);
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing.ts b/examples-testing/examples/webgpu_postprocessing.ts
new file mode 100644
index 000000000..7ae63a39d
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing.ts
@@ -0,0 +1,77 @@
+import * as THREE from 'three';
+import { pass, dotScreen, rgbShift } from 'three/tsl';
+
+let camera, renderer, postProcessing;
+let object;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    const scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 1000);
+
+    object = new THREE.Object3D();
+    scene.add(object);
+
+    const geometry = new THREE.SphereGeometry(1, 4, 4);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
+
+    for (let i = 0; i < 100; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
+        mesh.position.multiplyScalar(Math.random() * 400);
+        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
+        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
+        object.add(mesh);
+    }
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(1, 1, 1);
+    scene.add(light);
+
+    // postprocessing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    const scenePassColor = scenePass.getTextureNode();
+
+    const dotScreenPass = dotScreen(scenePassColor);
+    dotScreenPass.scale.value = 0.3;
+
+    const rgbShiftPass = rgbShift(dotScreenPass);
+    rgbShiftPass.amount.value = 0.001;
+
+    postProcessing.outputNode = rgbShiftPass;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    object.rotation.x += 0.005;
+    object.rotation.y += 0.01;
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_3dlut.ts b/examples-testing/examples/webgpu_postprocessing_3dlut.ts
new file mode 100644
index 000000000..27500a079
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_3dlut.ts
@@ -0,0 +1,139 @@
+import * as THREE from 'three';
+import { pass, texture3D, uniform, lut3D, renderOutput } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js';
+import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js';
+import { LUTImageLoader } from 'three/addons/loaders/LUTImageLoader.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const params = {
+    lut: 'Bourbon 64.CUBE',
+    intensity: 1,
+};
+
+const lutMap = {
+    'Bourbon 64.CUBE': null,
+    'Chemical 168.CUBE': null,
+    'Clayton 33.CUBE': null,
+    'Cubicle 99.CUBE': null,
+    'Remy 24.CUBE': null,
+    'Presetpro-Cinematic.3dl': null,
+    NeutralLUT: null,
+    'B&WLUT': null,
+    NightLUT: null,
+};
+
+let gui;
+let camera, scene, renderer;
+let postProcessing, lutPass;
+
+init();
+
+async function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', function (gltf) {
+            scene.add(gltf.scene);
+        });
+    });
+
+    const lutCubeLoader = new LUTCubeLoader();
+    const lutImageLoader = new LUTImageLoader();
+    const lut3dlLoader = new LUT3dlLoader();
+
+    for (const name in lutMap) {
+        if (/\.CUBE$/i.test(name)) {
+            lutMap[name] = lutCubeLoader.loadAsync('luts/' + name);
+        } else if (/\LUT$/i.test(name)) {
+            lutMap[name] = lutImageLoader.loadAsync(`luts/${name}.png`);
+        } else {
+            lutMap[name] = lut3dlLoader.loadAsync('luts/' + name);
+        }
+    }
+
+    const pendings = Object.values(lutMap);
+    await Promise.all(pendings);
+
+    for (const name in lutMap) {
+        lutMap[name] = await lutMap[name];
+    }
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    // post processing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    // ignore default output color transform ( toneMapping and outputColorSpace )
+    // use renderOutput() for control the sequence
+
+    postProcessing.outputColorTransform = false;
+
+    // scene pass
+
+    const scenePass = pass(scene, camera);
+    const outputPass = renderOutput(scenePass);
+
+    const lut = lutMap[params.lut];
+    lutPass = lut3D(outputPass, texture3D(lut.texture3D), lut.texture3D.image.width, uniform(1));
+
+    postProcessing.outputNode = lutPass;
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, -0.2);
+    controls.update();
+
+    gui = new GUI();
+    gui.add(params, 'lut', Object.keys(lutMap));
+    gui.add(params, 'intensity').min(0).max(1);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    lutPass.intensityNode.value = params.intensity;
+
+    if (lutMap[params.lut]) {
+        const lut = lutMap[params.lut];
+        lutPass.lutNode.value = lut.texture3D;
+        lutPass.size.value = lut.texture3D.image.width;
+    }
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_afterimage.ts b/examples-testing/examples/webgpu_postprocessing_afterimage.ts
new file mode 100644
index 000000000..d0775d49b
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_afterimage.ts
@@ -0,0 +1,70 @@
+import * as THREE from 'three';
+import { pass, afterImage } from 'three/tsl';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+let mesh, postProcessing, combinedPass;
+
+const params = {
+    damp: 0.96,
+};
+
+init();
+createGUI();
+
+function init() {
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 400;
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x000000, 1, 1000);
+
+    const geometry = new THREE.TorusKnotGeometry(100, 30, 100, 16);
+    const material = new THREE.MeshNormalMaterial();
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    // postprocessing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    const scenePassColor = scenePass.getTextureNode();
+
+    combinedPass = scenePassColor;
+    combinedPass = afterImage(combinedPass, params.damp);
+
+    postProcessing.outputNode = combinedPass;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function createGUI() {
+    const gui = new GUI({ title: 'Damp setting' });
+    gui.add(combinedPass.damp, 'value', 0, 1).step(0.001);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function render() {
+    mesh.rotation.x += 0.0075;
+    mesh.rotation.y += 0.015;
+
+    postProcessing.render();
+}
+
+function animate() {
+    render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_ao.ts b/examples-testing/examples/webgpu_postprocessing_ao.ts
new file mode 100644
index 000000000..432d641a0
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_ao.ts
@@ -0,0 +1,204 @@
+import * as THREE from 'three';
+import { pass, mrt, output, transformedNormalView, texture, ao, denoise } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer, postProcessing, controls, clock, stats, mixer;
+
+let aoPass, denoisePass, blendPassAO, blendPassDenoise, scenePassColor;
+
+const params = {
+    distanceExponent: 1,
+    distanceFallOff: 1,
+    radius: 0.25,
+    scale: 1,
+    thickness: 1,
+    denoised: true,
+    enabled: true,
+    denoiseRadius: 5,
+    lumaPhi: 5,
+    depthPhi: 5,
+    normalPhi: 5,
+};
+
+init();
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(5, 2, 8);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xbfe3dd);
+
+    clock = new THREE.Clock();
+
+    const hdrloader = new RGBELoader();
+    const envMap = await hdrloader.loadAsync('textures/equirectangular/quarry_01_1k.hdr');
+    envMap.mapping = THREE.EquirectangularReflectionMapping;
+
+    scene.environment = envMap;
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.target.set(0, 0.5, 0);
+    controls.update();
+    controls.enablePan = false;
+    controls.enableDamping = true;
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    //
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    scenePass.setMRT(
+        mrt({
+            output: output,
+            normal: transformedNormalView,
+        }),
+    );
+
+    scenePassColor = scenePass.getTextureNode('output');
+    const scenePassNormal = scenePass.getTextureNode('normal');
+    const scenePassDepth = scenePass.getTextureNode('depth');
+
+    // ao
+
+    aoPass = ao(scenePassDepth, scenePassNormal, camera);
+    blendPassAO = aoPass.getTextureNode().mul(scenePassColor);
+
+    // denoise (optional)
+
+    const noiseTexture = texture(generateNoise());
+    denoisePass = denoise(aoPass.getTextureNode(), scenePassDepth, scenePassNormal, noiseTexture, camera);
+    blendPassDenoise = denoisePass.mul(scenePassColor);
+
+    postProcessing.outputNode = blendPassDenoise;
+
+    //
+
+    const dracoLoader = new DRACOLoader();
+    dracoLoader.setDecoderPath('jsm/libs/draco/');
+    dracoLoader.setDecoderConfig({ type: 'js' });
+    const loader = new GLTFLoader();
+    loader.setDRACOLoader(dracoLoader);
+    loader.setPath('models/gltf/');
+
+    const gltf = await loader.loadAsync('LittlestTokyo.glb');
+
+    const model = gltf.scene;
+    model.position.set(1, 1, 0);
+    model.scale.set(0.01, 0.01, 0.01);
+    scene.add(model);
+
+    mixer = new THREE.AnimationMixer(model);
+    mixer.clipAction(gltf.animations[0]).play();
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.title('AO settings');
+    gui.add(params, 'distanceExponent').min(1).max(4).onChange(updateParameters);
+    gui.add(params, 'distanceFallOff').min(0.01).max(1).onChange(updateParameters);
+    gui.add(params, 'radius').min(0.01).max(1).onChange(updateParameters);
+    gui.add(params, 'scale').min(0.01).max(2).onChange(updateParameters);
+    gui.add(params, 'thickness').min(0.01).max(2).onChange(updateParameters);
+    gui.add(params, 'denoised').onChange(updatePassChain);
+    gui.add(params, 'enabled').onChange(updatePassChain);
+    const folder = gui.addFolder('Denoise settings');
+    folder.add(params, 'denoiseRadius').min(0.01).max(10).name('radius').onChange(updateParameters);
+    folder.add(params, 'lumaPhi').min(0.01).max(10).onChange(updateParameters);
+    folder.add(params, 'depthPhi').min(0.01).max(10).onChange(updateParameters);
+    folder.add(params, 'normalPhi').min(0.01).max(10).onChange(updateParameters);
+}
+
+function updatePassChain() {
+    if (params.enabled === true) {
+        if (params.denoised === true) {
+            postProcessing.outputNode = blendPassDenoise;
+        } else {
+            postProcessing.outputNode = blendPassAO;
+        }
+    } else {
+        postProcessing.outputNode = scenePassColor;
+    }
+
+    postProcessing.needsUpdate = true;
+}
+
+function updateParameters() {
+    aoPass.distanceExponent.value = params.distanceExponent;
+    aoPass.distanceFallOff.value = params.distanceFallOff;
+    aoPass.radius.value = params.radius;
+    aoPass.scale.value = params.scale;
+    aoPass.thickness.value = params.thickness;
+
+    denoisePass.radius.value = params.denoiseRadius;
+    denoisePass.lumaPhi.value = params.lumaPhi;
+    denoisePass.depthPhi.value = params.depthPhi;
+    denoisePass.normalPhi.value = params.normalPhi;
+}
+
+function generateNoise(size = 64) {
+    const simplex = new SimplexNoise();
+
+    const arraySize = size * size * 4;
+    const data = new Uint8Array(arraySize);
+
+    for (let i = 0; i < size; i++) {
+        for (let j = 0; j < size; j++) {
+            const x = i;
+            const y = j;
+
+            data[(i * size + j) * 4] = (simplex.noise(x, y) * 0.5 + 0.5) * 255;
+            data[(i * size + j) * 4 + 1] = (simplex.noise(x + size, y) * 0.5 + 0.5) * 255;
+            data[(i * size + j) * 4 + 2] = (simplex.noise(x, y + size) * 0.5 + 0.5) * 255;
+            data[(i * size + j) * 4 + 3] = (simplex.noise(x + size, y + size) * 0.5 + 0.5) * 255;
+        }
+    }
+
+    const noiseTexture = new THREE.DataTexture(data, size, size);
+    noiseTexture.wrapS = THREE.RepeatWrapping;
+    noiseTexture.wrapT = THREE.RepeatWrapping;
+    noiseTexture.needsUpdate = true;
+
+    return noiseTexture;
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (mixer) {
+        mixer.update(delta);
+    }
+
+    controls.update();
+
+    postProcessing.render();
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom.ts b/examples-testing/examples/webgpu_postprocessing_bloom.ts
new file mode 100644
index 000000000..d38a7abb1
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_bloom.ts
@@ -0,0 +1,127 @@
+import * as THREE from 'three';
+import { pass, bloom } from 'three/tsl';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, stats;
+let postProcessing, renderer, mixer, clock;
+
+const params = {
+    threshold: 0,
+    strength: 1,
+    radius: 0,
+    exposure: 1,
+};
+
+init();
+
+async function init() {
+    const container = document.getElementById('container');
+
+    clock = new THREE.Clock();
+
+    const scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(-5, 2.5, -3.5);
+    scene.add(camera);
+
+    scene.add(new THREE.AmbientLight(0xcccccc));
+
+    const pointLight = new THREE.PointLight(0xffffff, 100);
+    camera.add(pointLight);
+
+    const loader = new GLTFLoader();
+    const gltf = await loader.loadAsync('models/gltf/PrimaryIonDrive.glb');
+
+    const model = gltf.scene;
+    scene.add(model);
+
+    mixer = new THREE.AnimationMixer(model);
+    const clip = gltf.animations[0];
+    mixer.clipAction(clip.optimize()).play();
+
+    //
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ReinhardToneMapping;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    const scenePassColor = scenePass.getTextureNode('output');
+
+    const bloomPass = bloom(scenePassColor);
+
+    postProcessing.outputNode = scenePassColor.add(bloomPass);
+
+    //
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxPolarAngle = Math.PI * 0.5;
+    controls.minDistance = 3;
+    controls.maxDistance = 8;
+
+    //
+
+    const gui = new GUI();
+
+    const bloomFolder = gui.addFolder('bloom');
+
+    bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
+        bloomPass.threshold.value = value;
+    });
+
+    bloomFolder.add(params, 'strength', 0.0, 3.0).onChange(function (value) {
+        bloomPass.strength.value = value;
+    });
+
+    gui.add(params, 'radius', 0.0, 1.0)
+        .step(0.01)
+        .onChange(function (value) {
+            bloomPass.radius.value = value;
+        });
+
+    const toneMappingFolder = gui.addFolder('tone mapping');
+
+    toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
+        renderer.toneMappingExposure = Math.pow(value, 4.0);
+    });
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    mixer.update(delta);
+
+    postProcessing.render();
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts b/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
new file mode 100644
index 000000000..7a6569f94
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
@@ -0,0 +1,100 @@
+import * as THREE from 'three';
+import { pass, mrt, output, bloom, emissive } from 'three/tsl';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+let postProcessing;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
+    camera.position.set(-1.8, 0.6, 2.7);
+
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('moonless_golf_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+
+        // model
+
+        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
+        loader.load('DamagedHelmet.gltf', function (gltf) {
+            scene.add(gltf.scene);
+        });
+    });
+
+    //
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const scenePass = pass(scene, camera);
+    scenePass.setMRT(
+        mrt({
+            output,
+            emissive,
+        }),
+    );
+
+    const outputPass = scenePass.getTextureNode();
+    const emissivePass = scenePass.getTextureNode('emissive');
+
+    const bloomPass = bloom(emissivePass, 2.5, 0.5);
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    postProcessing.outputNode = outputPass.add(bloomPass);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.target.set(0, 0, -0.2);
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+
+    const bloomFolder = gui.addFolder('bloom');
+    bloomFolder.add(bloomPass.strength, 'value', 0.0, 5.0).name('strength');
+    bloomFolder.add(bloomPass.radius, 'value', 0.0, 1.0).name('radius');
+
+    const toneMappingFolder = gui.addFolder('tone mapping');
+    toneMappingFolder.add(renderer, 'toneMappingExposure', 0.1, 2).name('exposure');
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function render() {
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts b/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
new file mode 100644
index 000000000..55d6b6bbe
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
@@ -0,0 +1,122 @@
+import * as THREE from 'three';
+import { pass, mrt, output, float, bloom, uniform } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+// scene
+
+const scene = new THREE.Scene();
+
+const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
+camera.position.set(0, 0, 20);
+camera.lookAt(0, 0, 0);
+
+const geometry = new THREE.IcosahedronGeometry(1, 15);
+
+for (let i = 0; i < 50; i++) {
+    const color = new THREE.Color();
+    color.setHSL(Math.random(), 0.7, Math.random() * 0.2 + 0.05);
+
+    const bloomIntensity = Math.random() > 0.5 ? 1 : 0;
+
+    const material = new THREE.MeshBasicNodeMaterial({ color: color });
+    material.mrtNode = mrt({
+        bloomIntensity: uniform(bloomIntensity),
+    });
+
+    const sphere = new THREE.Mesh(geometry, material);
+    sphere.position.x = Math.random() * 10 - 5;
+    sphere.position.y = Math.random() * 10 - 5;
+    sphere.position.z = Math.random() * 10 - 5;
+    sphere.position.normalize().multiplyScalar(Math.random() * 4.0 + 2.0);
+    sphere.scale.setScalar(Math.random() * Math.random() + 0.5);
+    scene.add(sphere);
+}
+
+// renderer
+
+const renderer = new THREE.WebGPURenderer();
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setAnimationLoop(animate);
+renderer.toneMapping = THREE.NeutralToneMapping;
+document.body.appendChild(renderer.domElement);
+
+// post processing
+
+const scenePass = pass(scene, camera);
+scenePass.setMRT(
+    mrt({
+        output,
+        bloomIntensity: float(0), // default bloom intensity
+    }),
+);
+
+const outputPass = scenePass.getTextureNode();
+const bloomIntensityPass = scenePass.getTextureNode('bloomIntensity');
+
+const bloomPass = bloom(outputPass.mul(bloomIntensityPass));
+
+const postProcessing = new THREE.PostProcessing(renderer);
+postProcessing.outputColorTransform = false;
+postProcessing.outputNode = outputPass.add(bloomPass).renderOutput();
+
+// controls
+
+const controls = new OrbitControls(camera, renderer.domElement);
+controls.maxPolarAngle = Math.PI * 0.5;
+controls.minDistance = 1;
+controls.maxDistance = 100;
+
+// raycaster
+
+const raycaster = new THREE.Raycaster();
+const mouse = new THREE.Vector2();
+
+window.addEventListener('pointerdown', event => {
+    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
+
+    raycaster.setFromCamera(mouse, camera);
+
+    const intersects = raycaster.intersectObjects(scene.children, false);
+
+    if (intersects.length > 0) {
+        const material = intersects[0].object.material;
+
+        const bloomIntensity = material.mrtNode.get('bloomIntensity');
+        bloomIntensity.value = bloomIntensity.value === 0 ? 1 : 0;
+    }
+});
+
+// gui
+
+const gui = new GUI();
+
+const bloomFolder = gui.addFolder('bloom');
+bloomFolder.add(bloomPass.threshold, 'value', 0.0, 1.0).name('threshold');
+bloomFolder.add(bloomPass.strength, 'value', 0.0, 3).name('strength');
+bloomFolder.add(bloomPass.radius, 'value', 0.0, 1.0).name('radius');
+
+const toneMappingFolder = gui.addFolder('tone mapping');
+toneMappingFolder.add(renderer, 'toneMappingExposure', 0.1, 3).name('exposure');
+
+// events
+
+window.onresize = function () {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+};
+
+// animate
+
+function animate() {
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_difference.ts b/examples-testing/examples/webgpu_postprocessing_difference.ts
new file mode 100644
index 000000000..dc30eb604
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_difference.ts
@@ -0,0 +1,92 @@
+import * as THREE from 'three';
+import { pass, luminance, saturation } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { Timer } from 'three/addons/misc/Timer.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const params = {
+    speed: 0,
+};
+
+let camera, renderer, postProcessing;
+let timer, mesh, controls;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.NeutralToneMapping;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 100);
+    camera.position.set(1, 2, 3);
+
+    const scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x0487e2, 7, 25);
+    scene.background = new THREE.Color(0x0487e2);
+
+    timer = new Timer();
+
+    const texture = new THREE.TextureLoader().load('textures/crate.gif');
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    const geometry = new THREE.BoxGeometry();
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    // post processing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+
+    const currentTexture = scenePass.getTextureNode();
+    const previousTexture = scenePass.getPreviousTextureNode();
+
+    const frameDiff = previousTexture.sub(currentTexture).abs();
+
+    const saturationAmount = luminance(frameDiff).mul(1000).clamp(0, 3);
+
+    postProcessing.outputNode = saturation(currentTexture, saturationAmount);
+
+    //
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 2;
+    controls.maxDistance = 10;
+    controls.enableDamping = true;
+    controls.dampingFactor = 0.01;
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.add(params, 'speed', 0, 2);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    timer.update();
+
+    controls.update();
+
+    mesh.rotation.y += timer.getDelta() * 5 * params.speed;
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_dof.ts b/examples-testing/examples/webgpu_postprocessing_dof.ts
new file mode 100644
index 000000000..26d034fba
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_dof.ts
@@ -0,0 +1,160 @@
+import * as THREE from 'three';
+import { cubeTexture, positionWorld, oscSine, timerGlobal, pass, dof, uniform } from 'three/tsl';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+//
+
+let camera, scene, renderer, mesh, stats;
+
+let mouseX = 0,
+    mouseY = 0;
+
+let windowHalfX = window.innerWidth / 2;
+let windowHalfY = window.innerHeight / 2;
+
+let width = window.innerWidth;
+let height = window.innerHeight;
+
+let postProcessing;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, width / height, 1, 3000);
+    camera.position.z = 200;
+
+    scene = new THREE.Scene();
+
+    const path = 'textures/cube/SwedishRoyalCastle/';
+    const format = '.jpg';
+    const urls = [
+        path + 'px' + format,
+        path + 'nx' + format,
+        path + 'py' + format,
+        path + 'ny' + format,
+        path + 'pz' + format,
+        path + 'nz' + format,
+    ];
+
+    const xgrid = 14,
+        ygrid = 9,
+        zgrid = 14;
+    const count = xgrid * ygrid * zgrid;
+
+    const textureCube = new THREE.CubeTextureLoader().load(urls);
+    const cubeTextureNode = cubeTexture(textureCube);
+    const oscPos = oscSine(positionWorld.div(1000 /* scene distance */).add(timerGlobal(0.2 /* speed */)));
+
+    const geometry = new THREE.SphereGeometry(60, 20, 10);
+    const material = new THREE.MeshBasicNodeMaterial();
+    material.colorNode = cubeTextureNode.mul(oscPos);
+
+    mesh = new THREE.InstancedMesh(geometry, material, count);
+    scene.add(mesh);
+
+    const matrix = new THREE.Matrix4();
+
+    let index = 0;
+
+    for (let i = 0; i < xgrid; i++) {
+        for (let j = 0; j < ygrid; j++) {
+            for (let k = 0; k < zgrid; k++) {
+                const x = 200 * (i - xgrid / 2);
+                const y = 200 * (j - ygrid / 2);
+                const z = 200 * (k - zgrid / 2);
+
+                mesh.setMatrixAt(index, matrix.identity().setPosition(x, y, z));
+                index++;
+            }
+        }
+    }
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    const effectController = {
+        focus: uniform(500.0),
+        aperture: uniform(5),
+        maxblur: uniform(0.01),
+    };
+
+    // post processing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+
+    const scenePassColor = scenePass.getTextureNode();
+    const scenePassViewZ = scenePass.getViewZNode();
+
+    const dofPass = dof(
+        scenePassColor,
+        scenePassViewZ,
+        effectController.focus,
+        effectController.aperture.mul(0.00001),
+        effectController.maxblur,
+    );
+
+    postProcessing.outputNode = dofPass;
+
+    // controls
+
+    renderer.domElement.style.touchAction = 'none';
+    renderer.domElement.addEventListener('pointermove', onPointerMove);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // stats
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    // gui
+
+    const gui = new GUI();
+    gui.add(effectController.focus, 'value', 10.0, 3000.0, 10).name('focus');
+    gui.add(effectController.aperture, 'value', 0, 10, 0.1).name('aperture');
+    gui.add(effectController.maxblur, 'value', 0.0, 0.01, 0.001).name('maxblur');
+}
+
+function onPointerMove(event) {
+    if (event.isPrimary === false) return;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+function onWindowResize() {
+    windowHalfX = window.innerWidth / 2;
+    windowHalfY = window.innerHeight / 2;
+
+    width = window.innerWidth;
+    height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    render();
+    //stats.update();
+}
+
+function render() {
+    camera.position.x += (mouseX - camera.position.x) * 0.036;
+    camera.position.y += (-mouseY - camera.position.y) * 0.036;
+
+    camera.lookAt(scene.position);
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_fxaa.ts b/examples-testing/examples/webgpu_postprocessing_fxaa.ts
new file mode 100644
index 000000000..3221614b7
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_fxaa.ts
@@ -0,0 +1,122 @@
+import * as THREE from 'three';
+import { pass, fxaa, renderOutput } from 'three/tsl';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+const params = {
+    enabled: true,
+    animated: false,
+};
+
+let camera, scene, renderer, clock, group;
+let postProcessing;
+
+init();
+
+async function init() {
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
+    camera.position.z = 50;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0xffffff);
+
+    clock = new THREE.Clock();
+
+    //
+
+    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d);
+    hemiLight.position.set(0, 1000, 0);
+    scene.add(hemiLight);
+
+    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+    dirLight.position.set(-3000, 1000, -1000);
+    scene.add(dirLight);
+
+    //
+
+    group = new THREE.Group();
+
+    const geometry = new THREE.TetrahedronGeometry();
+    const material = new THREE.MeshStandardMaterial({ color: 0xf73232, flatShading: true });
+
+    for (let i = 0; i < 100; i++) {
+        const mesh = new THREE.Mesh(geometry, material);
+
+        mesh.position.x = Math.random() * 50 - 25;
+        mesh.position.y = Math.random() * 50 - 25;
+        mesh.position.z = Math.random() * 50 - 25;
+
+        mesh.scale.setScalar(Math.random() * 2 + 1);
+
+        mesh.rotation.x = Math.random() * Math.PI;
+        mesh.rotation.y = Math.random() * Math.PI;
+        mesh.rotation.z = Math.random() * Math.PI;
+
+        group.add(mesh);
+    }
+
+    scene.add(group);
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // post processing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    // ignore default output color transform ( toneMapping and outputColorSpace )
+    // use renderOutput() for control the sequence
+
+    postProcessing.outputColorTransform = false;
+
+    // scene pass
+
+    const scenePass = pass(scene, camera);
+    const outputPass = renderOutput(scenePass);
+
+    // FXAA must be computed in sRGB color space (so after tone mapping and color space conversion)
+
+    const fxaaPass = fxaa(outputPass);
+    postProcessing.outputNode = fxaaPass;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const gui = new GUI();
+    gui.title('FXAA settings');
+    gui.add(params, 'enabled').onChange(value => {
+        if (value === true) {
+            postProcessing.outputNode = fxaaPass;
+        } else {
+            postProcessing.outputNode = outputPass;
+        }
+
+        postProcessing.needsUpdate = true;
+    });
+    gui.add(params, 'animated');
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    const delta = clock.getDelta();
+
+    if (params.animated === true) {
+        group.rotation.y += delta * 0.1;
+    }
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_masking.ts b/examples-testing/examples/webgpu_postprocessing_masking.ts
new file mode 100644
index 000000000..a4f56b170
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_masking.ts
@@ -0,0 +1,85 @@
+import * as THREE from 'three';
+import { pass, texture } from 'three/tsl';
+
+let camera, postProcessing, renderer;
+let box, torus;
+
+init();
+
+function init() {
+    // scene
+
+    const baseScene = new THREE.Scene();
+    baseScene.background = new THREE.Color(0xe0e0e0);
+
+    const maskScene1 = new THREE.Scene();
+    box = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 4));
+    maskScene1.add(box);
+
+    const maskScene2 = new THREE.Scene();
+    torus = new THREE.Mesh(new THREE.TorusGeometry(3, 1, 16, 32));
+    maskScene2.add(torus);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.position.z = 10;
+
+    // textures
+
+    const texture1 = new THREE.TextureLoader().load('textures/758px-Canestra_di_frutta_(Caravaggio).jpg');
+    texture1.colorSpace = THREE.SRGBColorSpace;
+    texture1.minFilter = THREE.LinearFilter;
+    texture1.flipY = false;
+
+    const texture2 = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
+    texture2.colorSpace = THREE.SRGBColorSpace;
+    texture2.flipY = false;
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // post processing
+
+    const base = pass(baseScene, camera);
+    const sceneMask1 = pass(maskScene1, camera).a;
+    const sceneMask2 = pass(maskScene2, camera).a;
+
+    let compose = base;
+    compose = sceneMask1.mix(compose, texture(texture1));
+    compose = sceneMask2.mix(compose, texture(texture2));
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    postProcessing.outputNode = compose;
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    const time = performance.now() * 0.001 + 6000;
+
+    box.position.x = Math.cos(time / 1.5) * 2;
+    box.position.y = Math.sin(time) * 2;
+    box.rotation.x = time;
+    box.rotation.y = time / 2;
+
+    torus.position.x = Math.cos(time) * 2;
+    torus.position.y = Math.sin(time / 1.5) * 2;
+    torus.rotation.x = time;
+    torus.rotation.y = time / 2;
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_motion_blur.ts b/examples-testing/examples/webgpu_postprocessing_motion_blur.ts
new file mode 100644
index 000000000..7e86bb483
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_motion_blur.ts
@@ -0,0 +1,205 @@
+import * as THREE from 'three';
+import { pass, texture, motionBlur, uniform, output, mrt, mix, velocity, uv, viewportUV } from 'three/tsl';
+
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer;
+let boxLeft, boxRight, model, mixer, clock;
+let postProcessing;
+let controls;
+let stats;
+
+const params = {
+    speed: 1.0,
+};
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 30);
+    camera.position.set(0, 1.5, 4.5);
+
+    scene = new THREE.Scene();
+    scene.fog = new THREE.Fog(0x0487e2, 7, 25);
+
+    const sunLight = new THREE.DirectionalLight(0xffe499, 5);
+    sunLight.castShadow = true;
+    sunLight.shadow.camera.near = 0.1;
+    sunLight.shadow.camera.far = 10;
+    sunLight.shadow.camera.right = 2;
+    sunLight.shadow.camera.left = -2;
+    sunLight.shadow.camera.top = 2;
+    sunLight.shadow.camera.bottom = -2;
+    sunLight.shadow.mapSize.width = 2048;
+    sunLight.shadow.mapSize.height = 2048;
+    sunLight.shadow.bias = -0.001;
+    sunLight.position.set(4, 4, 2);
+
+    const waterAmbientLight = new THREE.HemisphereLight(0x333366, 0x74ccf4, 5);
+    const skyAmbientLight = new THREE.HemisphereLight(0x74ccf4, 0, 1);
+
+    scene.add(sunLight);
+    scene.add(skyAmbientLight);
+    scene.add(waterAmbientLight);
+
+    clock = new THREE.Clock();
+
+    // animated model
+
+    const loader = new GLTFLoader();
+    loader.load('models/gltf/Xbot.glb', function (gltf) {
+        model = gltf.scene;
+
+        model.rotation.y = Math.PI / 2;
+
+        model.traverse(function (child) {
+            if (child.isMesh) {
+                child.castShadow = true;
+                child.receiveShadow = true;
+            }
+        });
+
+        mixer = new THREE.AnimationMixer(model);
+
+        const action = mixer.clipAction(gltf.animations[3]);
+        action.play();
+
+        scene.add(model);
+    });
+
+    // textures
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const floorColor = textureLoader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
+    floorColor.wrapS = THREE.RepeatWrapping;
+    floorColor.wrapT = THREE.RepeatWrapping;
+    floorColor.colorSpace = THREE.SRGBColorSpace;
+
+    const floorNormal = textureLoader.load('textures/floors/FloorsCheckerboard_S_Normal.jpg');
+    floorNormal.wrapS = THREE.RepeatWrapping;
+    floorNormal.wrapT = THREE.RepeatWrapping;
+
+    // floor
+
+    const floorUV = uv().mul(5);
+
+    const floorMaterial = new THREE.MeshPhongNodeMaterial();
+    floorMaterial.colorNode = texture(floorColor, floorUV);
+
+    const floor = new THREE.Mesh(new THREE.BoxGeometry(15, 0.001, 15), floorMaterial);
+    floor.receiveShadow = true;
+
+    floor.position.set(0, 0, 0);
+    scene.add(floor);
+
+    const walls = new THREE.Mesh(
+        new THREE.BoxGeometry(15, 15, 15),
+        new THREE.MeshPhongNodeMaterial({ colorNode: floorMaterial.colorNode, side: THREE.BackSide }),
+    );
+    scene.add(walls);
+
+    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
+    map.colorSpace = THREE.SRGBColorSpace;
+
+    const geometry = new THREE.TorusGeometry(0.8);
+    const material = new THREE.MeshBasicMaterial({ map });
+
+    boxRight = new THREE.Mesh(geometry, material);
+    boxRight.position.set(3.5, 1.5, -4);
+    scene.add(boxRight);
+
+    boxLeft = new THREE.Mesh(geometry, material);
+    boxLeft.position.set(-3.5, 1.5, -4);
+    scene.add(boxLeft);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 1;
+    controls.maxDistance = 10;
+    controls.maxPolarAngle = Math.PI / 2;
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = 1;
+    controls.target.set(0, 1, 0);
+    controls.enableDamping = true;
+    controls.dampingFactor = 0.05;
+    controls.update();
+
+    // post-processing
+
+    const blurAmount = uniform(1);
+    const showVelocity = uniform(0);
+
+    const scenePass = pass(scene, camera);
+
+    scenePass.setMRT(
+        mrt({
+            output,
+            velocity,
+        }),
+    );
+
+    const beauty = scenePass.getTextureNode();
+    const vel = scenePass.getTextureNode('velocity').mul(blurAmount);
+
+    const mBlur = motionBlur(beauty, vel);
+
+    const vignet = viewportUV.distance(0.5).remap(0.6, 1).mul(2).clamp().oneMinus();
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    postProcessing.outputNode = mix(mBlur, vel, showVelocity).mul(vignet);
+
+    //
+
+    const gui = new GUI();
+    gui.title('Motion Blur Settings');
+    gui.add(controls, 'autoRotate');
+    gui.add(blurAmount, 'value', 0, 3).name('blur amount');
+    gui.add(params, 'speed', 0, 2);
+    gui.add(showVelocity, 'value', 0, 1).name('show velocity');
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    stats.update();
+
+    controls.update();
+
+    const delta = clock.getDelta();
+    const speed = params.speed;
+
+    boxRight.rotation.y += delta * 4 * speed;
+    boxLeft.scale.setScalar(1 + Math.sin(clock.elapsedTime * 10 * speed) * 0.2);
+
+    if (model) {
+        mixer.update(delta * speed);
+    }
+
+    postProcessing.render();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_pixel.ts b/examples-testing/examples/webgpu_postprocessing_pixel.ts
new file mode 100644
index 000000000..d7e51008d
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_pixel.ts
@@ -0,0 +1,234 @@
+import * as THREE from 'three';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+import { uniform, pixelationPass } from 'three/tsl';
+
+let camera, scene, renderer, postProcessing, crystalMesh, clock;
+let gui, effectController;
+
+init();
+
+function init() {
+    const aspectRatio = window.innerWidth / window.innerHeight;
+
+    camera = new THREE.OrthographicCamera(-aspectRatio, aspectRatio, 1, -1, 0.1, 10);
+    camera.position.y = 2 * Math.tan(Math.PI / 6);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x151729);
+
+    clock = new THREE.Clock();
+
+    // textures
+
+    const loader = new THREE.TextureLoader();
+    const texChecker = pixelTexture(loader.load('textures/checker.png'));
+    const texChecker2 = pixelTexture(loader.load('textures/checker.png'));
+    texChecker.repeat.set(3, 3);
+    texChecker2.repeat.set(1.5, 1.5);
+
+    // meshes
+
+    const boxMaterial = new THREE.MeshPhongMaterial({ map: texChecker2 });
+
+    function addBox(boxSideLength, x, z, rotation) {
+        const mesh = new THREE.Mesh(new THREE.BoxGeometry(boxSideLength, boxSideLength, boxSideLength), boxMaterial);
+        mesh.castShadow = true;
+        mesh.receiveShadow = true;
+        mesh.rotation.y = rotation;
+        mesh.position.y = boxSideLength / 2;
+        mesh.position.set(x, boxSideLength / 2 + 0.0001, z);
+        scene.add(mesh);
+        return mesh;
+    }
+
+    addBox(0.4, 0, 0, Math.PI / 4);
+    addBox(0.5, -0.5, -0.5, Math.PI / 4);
+
+    const planeSideLength = 2;
+    const planeMesh = new THREE.Mesh(
+        new THREE.PlaneGeometry(planeSideLength, planeSideLength),
+        new THREE.MeshPhongMaterial({ map: texChecker }),
+    );
+    planeMesh.receiveShadow = true;
+    planeMesh.rotation.x = -Math.PI / 2;
+    scene.add(planeMesh);
+
+    const radius = 0.2;
+    const geometry = new THREE.IcosahedronGeometry(radius);
+    crystalMesh = new THREE.Mesh(
+        geometry,
+        new THREE.MeshPhongMaterial({
+            color: 0x68b7e9,
+            emissive: 0x4f7e8b,
+            shininess: 10,
+            specular: 0xffffff,
+        }),
+    );
+    crystalMesh.receiveShadow = true;
+    crystalMesh.castShadow = true;
+    scene.add(crystalMesh);
+
+    // lights
+
+    scene.add(new THREE.AmbientLight(0x757f8e, 3));
+
+    const directionalLight = new THREE.DirectionalLight(0xfffecd, 1.5);
+    directionalLight.position.set(100, 100, 100);
+    directionalLight.castShadow = true;
+    directionalLight.shadow.mapSize.set(2048, 2048);
+    directionalLight.shadow.bias = -0.0001;
+    scene.add(directionalLight);
+
+    const spotLight = new THREE.SpotLight(0xffc100, 10, 10, Math.PI / 16, 0.02, 2);
+    spotLight.position.set(2, 2, 0);
+    const target = spotLight.target;
+    scene.add(target);
+    target.position.set(0, 0, 0);
+    spotLight.castShadow = true;
+    spotLight.shadow.bias = -0.001;
+    scene.add(spotLight);
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.shadowMap.enabled = true;
+    renderer.shadowMap.type = THREE.BasicShadowMap;
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    effectController = {
+        pixelSize: uniform(6),
+        normalEdgeStrength: uniform(0.3),
+        depthEdgeStrength: uniform(0.4),
+        pixelAlignedPanning: true,
+    };
+
+    postProcessing = new THREE.PostProcessing(renderer);
+    const scenePass = pixelationPass(
+        scene,
+        camera,
+        effectController.pixelSize,
+        effectController.normalEdgeStrength,
+        effectController.depthEdgeStrength,
+    );
+    postProcessing.outputNode = scenePass;
+
+    window.addEventListener('resize', onWindowResize);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.maxZoom = 2;
+
+    // gui
+
+    gui = new GUI();
+    gui.add(effectController.pixelSize, 'value', 1, 16, 1).name('Pixel Size');
+    gui.add(effectController.normalEdgeStrength, 'value', 0, 2, 0.05).name('Normal Edge Strength');
+    gui.add(effectController.depthEdgeStrength, 'value', 0, 1, 0.05).name('Depth Edge Strength');
+    gui.add(effectController, 'pixelAlignedPanning');
+}
+
+function onWindowResize() {
+    const aspectRatio = window.innerWidth / window.innerHeight;
+    camera.left = -aspectRatio;
+    camera.right = aspectRatio;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const t = clock.getElapsedTime();
+
+    crystalMesh.material.emissiveIntensity = Math.sin(t * 3) * 0.5 + 0.5;
+    crystalMesh.position.y = 0.7 + Math.sin(t * 2) * 0.05;
+    crystalMesh.rotation.y = stopGoEased(t, 2, 4) * 2 * Math.PI;
+
+    const rendererSize = renderer.getSize(new THREE.Vector2());
+    const aspectRatio = rendererSize.x / rendererSize.y;
+
+    if (effectController.pixelAlignedPanning) {
+        const pixelSize = effectController.pixelSize.value;
+
+        pixelAlignFrustum(
+            camera,
+            aspectRatio,
+            Math.floor(rendererSize.x / pixelSize),
+            Math.floor(rendererSize.y / pixelSize),
+        );
+    } else if (camera.left != -aspectRatio || camera.top != 1.0) {
+        // Reset the Camera Frustum if it has been modified
+        camera.left = -aspectRatio;
+        camera.right = aspectRatio;
+        camera.top = 1.0;
+        camera.bottom = -1.0;
+        camera.updateProjectionMatrix();
+    }
+
+    postProcessing.render();
+}
+
+// Helper functions
+
+function pixelTexture(texture) {
+    texture.minFilter = THREE.NearestFilter;
+    texture.magFilter = THREE.NearestFilter;
+    texture.generateMipmaps = false;
+    texture.wrapS = THREE.RepeatWrapping;
+    texture.wrapT = THREE.RepeatWrapping;
+    texture.colorSpace = THREE.SRGBColorSpace;
+    return texture;
+}
+
+function easeInOutCubic(x) {
+    return x ** 2 * 3 - x ** 3 * 2;
+}
+
+function linearStep(x, edge0, edge1) {
+    const w = edge1 - edge0;
+    const m = 1 / w;
+    const y0 = -m * edge0;
+    return THREE.MathUtils.clamp(y0 + m * x, 0, 1);
+}
+
+function stopGoEased(x, downtime, period) {
+    const cycle = (x / period) | 0;
+    const tween = x - cycle * period;
+    const linStep = easeInOutCubic(linearStep(tween, downtime, period));
+    return cycle + linStep;
+}
+
+function pixelAlignFrustum(camera, aspectRatio, pixelsPerScreenWidth, pixelsPerScreenHeight) {
+    // 0. Get Pixel Grid Units
+    const worldScreenWidth = (camera.right - camera.left) / camera.zoom;
+    const worldScreenHeight = (camera.top - camera.bottom) / camera.zoom;
+    const pixelWidth = worldScreenWidth / pixelsPerScreenWidth;
+    const pixelHeight = worldScreenHeight / pixelsPerScreenHeight;
+
+    // 1. Project the current camera position along its local rotation bases
+    const camPos = new THREE.Vector3();
+    camera.getWorldPosition(camPos);
+    const camRot = new THREE.Quaternion();
+    camera.getWorldQuaternion(camRot);
+    const camRight = new THREE.Vector3(1.0, 0.0, 0.0).applyQuaternion(camRot);
+    const camUp = new THREE.Vector3(0.0, 1.0, 0.0).applyQuaternion(camRot);
+    const camPosRight = camPos.dot(camRight);
+    const camPosUp = camPos.dot(camUp);
+
+    // 2. Find how far along its position is along these bases in pixel units
+    const camPosRightPx = camPosRight / pixelWidth;
+    const camPosUpPx = camPosUp / pixelHeight;
+
+    // 3. Find the fractional pixel units and convert to world units
+    const fractX = camPosRightPx - Math.round(camPosRightPx);
+    const fractY = camPosUpPx - Math.round(camPosUpPx);
+
+    // 4. Add fractional world units to the left/right top/bottom to align with the pixel grid
+    camera.left = -aspectRatio - fractX * pixelWidth;
+    camera.right = aspectRatio - fractX * pixelWidth;
+    camera.top = 1.0 - fractY * pixelHeight;
+    camera.bottom = -1.0 - fractY * pixelHeight;
+    camera.updateProjectionMatrix();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_sobel.ts b/examples-testing/examples/webgpu_postprocessing_sobel.ts
new file mode 100644
index 000000000..01aa16ecd
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_sobel.ts
@@ -0,0 +1,89 @@
+import * as THREE from 'three';
+import { pass, sobel } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+let postProcessing;
+
+const params = {
+    enable: true,
+};
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(0, 1, 3);
+    camera.lookAt(scene.position);
+
+    //
+
+    const ambientLight = new THREE.AmbientLight(0xe7e7e7);
+    scene.add(ambientLight);
+
+    const pointLight = new THREE.PointLight(0xffffff, 20);
+    camera.add(pointLight);
+    scene.add(camera);
+
+    //
+
+    const geometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 32);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffff00 });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableZoom = false;
+
+    // postprocessing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePass = pass(scene, camera);
+    const scenePassColor = scenePass.getTextureNode();
+
+    postProcessing.outputNode = sobel(scenePassColor);
+
+    //
+
+    const gui = new GUI();
+
+    gui.add(params, 'enable');
+    gui.open();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    if (params.enable === true) {
+        postProcessing.render();
+    } else {
+        renderer.render(scene, camera);
+    }
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_ssaa.ts b/examples-testing/examples/webgpu_postprocessing_ssaa.ts
new file mode 100644
index 000000000..76e3a95cd
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_ssaa.ts
@@ -0,0 +1,181 @@
+import * as THREE from 'three';
+import { ssaaPass } from 'three/tsl';
+
+import { Timer } from 'three/addons/misc/Timer.js';
+import Stats from 'three/addons/libs/stats.module.js';
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let scene, mesh, renderer, postProcessing;
+let camera, ssaaRenderPass;
+let gui, stats, timer;
+
+const params = {
+    sampleLevel: 3,
+    camera: 'perspective',
+    clearColor: 'black',
+    clearAlpha: 1.0,
+    viewOffsetX: 0,
+    autoRotate: true,
+};
+
+init();
+
+clearGui();
+
+function clearGui() {
+    if (gui) gui.destroy();
+
+    gui = new GUI();
+
+    gui.add(params, 'sampleLevel', {
+        'Level 0: 1 Sample': 0,
+        'Level 1: 2 Samples': 1,
+        'Level 2: 4 Samples': 2,
+        'Level 3: 8 Samples': 3,
+        'Level 4: 16 Samples': 4,
+        'Level 5: 32 Samples': 5,
+    });
+    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
+    gui.add(params, 'clearAlpha', 0, 1);
+    gui.add(params, 'viewOffsetX', -100, 100);
+    gui.add(params, 'autoRotate');
+
+    gui.open();
+}
+
+function init() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    stats = new Stats();
+    document.body.appendChild(stats.dom);
+
+    timer = new Timer();
+
+    camera = new THREE.PerspectiveCamera(65, width / height, 3, 10);
+    camera.position.z = 7;
+    camera.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
+
+    scene = new THREE.Scene();
+
+    const group = new THREE.Group();
+    scene.add(group);
+
+    const light = new THREE.PointLight(0xefffef, 500);
+    light.position.z = 10;
+    light.position.y = -10;
+    light.position.x = -10;
+    scene.add(light);
+
+    const light2 = new THREE.PointLight(0xffefef, 500);
+    light2.position.z = 10;
+    light2.position.x = -10;
+    light2.position.y = 10;
+    scene.add(light2);
+
+    const light3 = new THREE.PointLight(0xefefff, 500);
+    light3.position.z = 10;
+    light3.position.x = 10;
+    light3.position.y = -10;
+    scene.add(light3);
+
+    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
+    scene.add(light4);
+
+    const geometry = new THREE.SphereGeometry(3, 48, 24);
+    const material = new THREE.MeshStandardMaterial();
+
+    mesh = new THREE.InstancedMesh(geometry, material, 120);
+
+    const dummy = new THREE.Mesh();
+    const color = new THREE.Color();
+
+    for (let i = 0; i < mesh.count; i++) {
+        dummy.position.x = Math.random() * 4 - 2;
+        dummy.position.y = Math.random() * 4 - 2;
+        dummy.position.z = Math.random() * 4 - 2;
+        dummy.rotation.x = Math.random();
+        dummy.rotation.y = Math.random();
+        dummy.rotation.z = Math.random();
+        dummy.scale.setScalar(Math.random() * 0.2 + 0.05);
+
+        dummy.updateMatrix();
+
+        color.setHSL(Math.random(), 1.0, 0.3);
+
+        mesh.setMatrixAt(i, dummy.matrix);
+        mesh.setColorAt(i, color);
+    }
+
+    scene.add(mesh);
+
+    // postprocessing
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    ssaaRenderPass = ssaaPass(scene, camera);
+    const scenePassColor = ssaaRenderPass.getTextureNode();
+
+    postProcessing.outputNode = scenePassColor;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+
+    camera.aspect = width / height;
+    camera.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(width, height);
+}
+
+function animate() {
+    timer.update();
+
+    if (params.autoRotate) {
+        const delta = timer.getDelta();
+
+        mesh.rotation.x += delta * 0.25;
+        mesh.rotation.y += delta * 0.5;
+    }
+
+    let newColor = ssaaRenderPass.clearColor;
+
+    switch (params.clearColor) {
+        case 'blue':
+            newColor = 0x0000ff;
+            break;
+        case 'red':
+            newColor = 0xff0000;
+            break;
+        case 'green':
+            newColor = 0x00ff00;
+            break;
+        case 'white':
+            newColor = 0xffffff;
+            break;
+        case 'black':
+            newColor = 0x000000;
+            break;
+    }
+
+    ssaaRenderPass.clearColor.set(newColor);
+    ssaaRenderPass.clearAlpha = params.clearAlpha;
+
+    ssaaRenderPass.sampleLevel = params.sampleLevel;
+
+    camera.view.offsetX = params.viewOffsetX;
+
+    postProcessing.render();
+
+    stats.update();
+}
diff --git a/examples-testing/examples/webgpu_postprocessing_transition.ts b/examples-testing/examples/webgpu_postprocessing_transition.ts
new file mode 100644
index 000000000..b66bad12c
--- /dev/null
+++ b/examples-testing/examples/webgpu_postprocessing_transition.ts
@@ -0,0 +1,200 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import TWEEN from 'three/addons/libs/tween.module.js';
+import { uniform, transition, pass } from 'three/tsl';
+
+let renderer, postProcessing, transitionController, transitionPass;
+
+const textures = [];
+const clock = new THREE.Clock();
+
+const effectController = {
+    animateScene: true,
+    animateTransition: true,
+    transition: 0,
+    _transition: uniform(0),
+    useTexture: true,
+    _useTexture: uniform(1),
+    texture: 5,
+    cycle: true,
+    threshold: uniform(0.1),
+};
+
+function generateInstancedMesh(geometry, material, count) {
+    const mesh = new THREE.InstancedMesh(geometry, material, count);
+
+    const dummy = new THREE.Object3D();
+    const color = new THREE.Color();
+
+    for (let i = 0; i < count; i++) {
+        dummy.position.x = Math.random() * 100 - 50;
+        dummy.position.y = Math.random() * 60 - 30;
+        dummy.position.z = Math.random() * 80 - 40;
+
+        dummy.rotation.x = Math.random() * 2 * Math.PI;
+        dummy.rotation.y = Math.random() * 2 * Math.PI;
+        dummy.rotation.z = Math.random() * 2 * Math.PI;
+
+        dummy.scale.x = Math.random() * 2 + 1;
+
+        if (geometry.type === 'BoxGeometry') {
+            dummy.scale.y = Math.random() * 2 + 1;
+            dummy.scale.z = Math.random() * 2 + 1;
+        } else {
+            dummy.scale.y = dummy.scale.x;
+            dummy.scale.z = dummy.scale.x;
+        }
+
+        dummy.updateMatrix();
+
+        mesh.setMatrixAt(i, dummy.matrix);
+        mesh.setColorAt(i, color.setScalar(0.1 + 0.9 * Math.random()));
+    }
+
+    return mesh;
+}
+
+function FXScene(geometry, rotationSpeed, backgroundColor) {
+    const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.z = 20;
+
+    // Setup scene
+    const scene = new THREE.Scene();
+    scene.background = new THREE.Color(backgroundColor);
+    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 1, 4);
+    scene.add(light);
+
+    this.rotationSpeed = rotationSpeed;
+
+    const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
+    const material = new THREE.MeshPhongNodeMaterial({ color: color, flatShading: true });
+    const mesh = generateInstancedMesh(geometry, material, 500);
+    scene.add(mesh);
+
+    this.scene = scene;
+    this.camera = camera;
+    this.mesh = mesh;
+
+    this.update = function (delta) {
+        if (effectController.animateScene) {
+            mesh.rotation.x += this.rotationSpeed.x * delta;
+            mesh.rotation.y += this.rotationSpeed.y * delta;
+            mesh.rotation.z += this.rotationSpeed.z * delta;
+        }
+    };
+
+    this.resize = function () {
+        camera.aspect = window.innerWidth / window.innerHeight;
+        camera.updateProjectionMatrix();
+    };
+}
+
+const fxSceneA = new FXScene(new THREE.BoxGeometry(2, 2, 2), new THREE.Vector3(0, -0.4, 0), 0xffffff);
+const fxSceneB = new FXScene(new THREE.IcosahedronGeometry(1, 1), new THREE.Vector3(0, 0.2, 0.1), 0x000000);
+
+function init() {
+    // Initialize textures
+
+    const loader = new THREE.TextureLoader();
+
+    for (let i = 0; i < 6; i++) {
+        textures[i] = loader.load('textures/transition/transition' + (i + 1) + '.png');
+    }
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    postProcessing = new THREE.PostProcessing(renderer);
+
+    const scenePassA = pass(fxSceneA.scene, fxSceneA.camera);
+    const scenePassB = pass(fxSceneB.scene, fxSceneB.camera);
+
+    transitionPass = transition(
+        scenePassA,
+        scenePassB,
+        new THREE.TextureNode(textures[effectController.texture]),
+        effectController._transition,
+        effectController.threshold,
+        effectController._useTexture,
+    );
+
+    postProcessing.outputNode = transitionPass;
+
+    const gui = new GUI();
+
+    gui.add(effectController, 'animateScene').name('Animate Scene');
+    gui.add(effectController, 'animateTransition').name('Animate Transition');
+    transitionController = gui
+        .add(effectController, 'transition', 0, 1, 0.01)
+        .name('transition')
+        .onChange(() => {
+            effectController._transition.value = effectController.transition;
+        });
+    gui.add(effectController, 'useTexture').onChange(() => {
+        const value = effectController.useTexture ? 1 : 0;
+        effectController._useTexture.value = value;
+    });
+    gui.add(effectController, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 });
+    gui.add(effectController, 'cycle');
+    gui.add(effectController.threshold, 'value', 0, 1, 0.01).name('threshold');
+}
+
+window.addEventListener('resize', onWindowResize);
+
+function onWindowResize() {
+    fxSceneA.resize();
+    fxSceneB.resize();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+new TWEEN.Tween(effectController)
+    .to({ transition: 1 }, 1500)
+    .onUpdate(function () {
+        transitionController.setValue(effectController.transition);
+
+        // Change the current alpha texture after each transition
+        if (effectController.cycle) {
+            if (effectController.transition == 0 || effectController.transition == 1) {
+                effectController.texture = (effectController.texture + 1) % textures.length;
+            }
+        }
+    })
+    .repeat(Infinity)
+    .delay(2000)
+    .yoyo(true)
+    .start();
+
+function animate() {
+    if (effectController.animateTransition) TWEEN.update();
+
+    if (textures[effectController.texture]) {
+        const mixTexture = textures[effectController.texture];
+        transitionPass.mixTextureNode.value = mixTexture;
+    }
+
+    const delta = clock.getDelta();
+    fxSceneA.update(delta);
+    fxSceneB.update(delta);
+
+    render();
+}
+
+function render() {
+    // Prevent render both scenes when it's not necessary
+    if (effectController.transition === 0) {
+        renderer.render(fxSceneB.scene, fxSceneB.camera);
+    } else if (effectController.transition === 1) {
+        renderer.render(fxSceneA.scene, fxSceneA.camera);
+    } else {
+        postProcessing.render();
+    }
+}
+
+init();
diff --git a/examples-testing/examples/webgpu_procedural_texture.ts b/examples-testing/examples/webgpu_procedural_texture.ts
new file mode 100644
index 000000000..84e8ba9e4
--- /dev/null
+++ b/examples-testing/examples/webgpu_procedural_texture.ts
@@ -0,0 +1,74 @@
+import * as THREE from 'three';
+import { checker, uv, uniform, gaussianBlur, convertToTexture } from 'three/tsl';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+let camera, scene, renderer;
+
+init();
+render();
+
+function init() {
+    const aspect = window.innerWidth / window.innerHeight;
+    camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0, 2);
+    camera.position.z = 1;
+
+    scene = new THREE.Scene();
+
+    // procedural to texture
+
+    const uvScale = uniform(4);
+    const blurAmount = uniform(0.5);
+
+    const procedural = checker(uv().mul(uvScale));
+    const proceduralToTexture = convertToTexture(procedural, 512, 512); // ( node, width, height )
+
+    const colorNode = gaussianBlur(proceduralToTexture, blurAmount, 10);
+
+    // extra
+
+    //proceduralToTexture.autoUpdate = false; // update just once
+    //proceduralToTexture.textureNeedsUpdate = true; // manually update
+
+    // scene
+
+    const material = new THREE.MeshBasicNodeMaterial();
+    material.colorNode = colorNode;
+
+    const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
+    scene.add(plane);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(render);
+    document.body.appendChild(renderer.domElement);
+
+    window.addEventListener('resize', onWindowResize);
+
+    // gui
+
+    const gui = new GUI();
+    gui.add(uvScale, 'value', 1, 10).name('uv scale ( before rtt )');
+    gui.add(blurAmount, 'value', 0, 2).name('blur amount ( after rtt )');
+    gui.add(proceduralToTexture, 'autoUpdate').name('auto update');
+}
+
+function onWindowResize() {
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    const aspect = window.innerWidth / window.innerHeight;
+
+    const frustumHeight = camera.top - camera.bottom;
+
+    camera.left = (-frustumHeight * aspect) / 2;
+    camera.right = (frustumHeight * aspect) / 2;
+
+    camera.updateProjectionMatrix();
+}
+
+function render() {
+    renderer.renderAsync(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_refraction.ts b/examples-testing/examples/webgpu_refraction.ts
new file mode 100644
index 000000000..a410c04f7
--- /dev/null
+++ b/examples-testing/examples/webgpu_refraction.ts
@@ -0,0 +1,141 @@
+import * as THREE from 'three';
+import { viewportSafeUV, viewportSharedTexture, viewportUV, texture, uv } from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer;
+
+let cameraControls;
+
+let smallSphere;
+
+init();
+
+function init() {
+    // scene
+    scene = new THREE.Scene();
+
+    // camera
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
+    camera.position.set(0, 50, 160);
+
+    //
+
+    const geometry = new THREE.IcosahedronGeometry(5, 0);
+    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x7b7b7b, flatShading: true });
+    smallSphere = new THREE.Mesh(geometry, material);
+    scene.add(smallSphere);
+
+    // textures
+
+    const loader = new THREE.TextureLoader();
+
+    const floorNormal = loader.load('textures/floors/FloorsCheckerboard_S_Normal.jpg');
+    floorNormal.wrapS = THREE.RepeatWrapping;
+    floorNormal.wrapT = THREE.RepeatWrapping;
+
+    // refractor
+
+    const verticalNormalScale = 0.1;
+    const verticalUVOffset = texture(floorNormal, uv().mul(5)).xy.mul(2).sub(1).mul(verticalNormalScale);
+
+    const refractorUV = viewportUV.add(verticalUVOffset);
+    const verticalRefractor = viewportSharedTexture(viewportSafeUV(refractorUV));
+
+    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
+
+    const planeRefractor = new THREE.Mesh(
+        planeGeo,
+        new THREE.MeshBasicNodeMaterial({
+            backdropNode: verticalRefractor,
+        }),
+    );
+    planeRefractor.material.transparent = true;
+    planeRefractor.position.y = 50;
+    scene.add(planeRefractor);
+
+    // walls
+
+    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeTop.position.y = 100;
+    planeTop.rotateX(Math.PI / 2);
+    scene.add(planeTop);
+
+    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
+    planeBottom.rotateX(-Math.PI / 2);
+    scene.add(planeBottom);
+
+    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
+    planeBack.position.z = -50;
+    planeBack.position.y = 50;
+    scene.add(planeBack);
+
+    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
+    planeRight.position.x = 50;
+    planeRight.position.y = 50;
+    planeRight.rotateY(-Math.PI / 2);
+    scene.add(planeRight);
+
+    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
+    planeLeft.position.x = -50;
+    planeLeft.position.y = 50;
+    planeLeft.rotateY(Math.PI / 2);
+    scene.add(planeLeft);
+
+    // lights
+
+    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
+    mainLight.position.y = 60;
+    scene.add(mainLight);
+
+    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
+    greenLight.position.set(550, 50, 0);
+    scene.add(greenLight);
+
+    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
+    redLight.position.set(-550, 50, 0);
+    scene.add(redLight);
+
+    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
+    blueLight.position.set(0, 50, 550);
+    scene.add(blueLight);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer(/*{ antialias: true }*/);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    cameraControls = new OrbitControls(camera, renderer.domElement);
+    cameraControls.target.set(0, 50, 0);
+    cameraControls.maxDistance = 400;
+    cameraControls.minDistance = 10;
+    cameraControls.update();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const timer = Date.now() * 0.01;
+
+    smallSphere.position.set(
+        Math.cos(timer * 0.1) * 30,
+        Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
+        Math.sin(timer * 0.1) * 30,
+    );
+    smallSphere.rotation.y = Math.PI / 2 - timer * 0.1;
+    smallSphere.rotation.z = timer * 0.8;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_sky.ts b/examples-testing/examples/webgpu_sky.ts
new file mode 100644
index 000000000..097d06af6
--- /dev/null
+++ b/examples-testing/examples/webgpu_sky.ts
@@ -0,0 +1,95 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
+
+let camera, scene, renderer;
+
+let sky, sun;
+
+init();
+
+function initSky() {
+    // Add Sky
+    sky = new SkyMesh();
+    sky.scale.setScalar(450000);
+    scene.add(sky);
+
+    sun = new THREE.Vector3();
+
+    /// GUI
+
+    const effectController = {
+        turbidity: 10,
+        rayleigh: 3,
+        mieCoefficient: 0.005,
+        mieDirectionalG: 0.7,
+        elevation: 2,
+        azimuth: 180,
+        exposure: renderer.toneMappingExposure,
+    };
+
+    function guiChanged() {
+        sky.turbidity.value = effectController.turbidity;
+        sky.rayleigh.value = effectController.rayleigh;
+        sky.mieCoefficient.value = effectController.mieCoefficient;
+        sky.mieDirectionalG.value = effectController.mieDirectionalG;
+
+        const phi = THREE.MathUtils.degToRad(90 - effectController.elevation);
+        const theta = THREE.MathUtils.degToRad(effectController.azimuth);
+
+        sun.setFromSphericalCoords(1, phi, theta);
+
+        sky.sunPosition.value.copy(sun);
+
+        renderer.toneMappingExposure = effectController.exposure;
+    }
+
+    const gui = new GUI();
+
+    gui.add(effectController, 'turbidity', 0.0, 20.0, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'rayleigh', 0.0, 4, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'mieCoefficient', 0.0, 0.1, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'mieDirectionalG', 0.0, 1, 0.001).onChange(guiChanged);
+    gui.add(effectController, 'elevation', 0, 90, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'azimuth', -180, 180, 0.1).onChange(guiChanged);
+    gui.add(effectController, 'exposure', 0, 1, 0.0001).onChange(guiChanged);
+
+    guiChanged();
+}
+
+function init() {
+    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 2000000);
+    camera.position.set(0, 100, 2000);
+
+    scene = new THREE.Scene();
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 0.5;
+    document.body.appendChild(renderer.domElement);
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    //controls.maxPolarAngle = Math.PI / 2;
+    controls.enableZoom = false;
+    controls.enablePan = false;
+
+    initSky();
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_texture2darray_compressed.ts b/examples-testing/examples/webgpu_texture2darray_compressed.ts
new file mode 100644
index 000000000..3e8bf7ee6
--- /dev/null
+++ b/examples-testing/examples/webgpu_texture2darray_compressed.ts
@@ -0,0 +1,84 @@
+import * as THREE from 'three';
+
+import { texture, uniform, uv } from 'three/tsl';
+
+import Stats from 'three/addons/libs/stats.module.js';
+import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+
+let camera, scene, mesh, renderer, stats, clock;
+
+const depth = uniform(0);
+
+const planeWidth = 50;
+const planeHeight = 25;
+
+let depthStep = 1;
+
+init();
+
+async function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
+    camera.position.z = 70;
+
+    scene = new THREE.Scene();
+
+    //
+    clock = new THREE.Clock();
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    //
+
+    const ktx2Loader = new KTX2Loader();
+    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
+    await ktx2Loader.detectSupportAsync(renderer);
+
+    ktx2Loader.load('textures/spiritedaway.ktx2', function (texturearray) {
+        const material = new THREE.NodeMaterial();
+
+        material.colorNode = texture(texturearray, uv().flipY()).depth(depth);
+        const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight);
+
+        mesh = new THREE.Mesh(geometry, material);
+
+        scene.add(mesh);
+    });
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    if (mesh) {
+        const delta = clock.getDelta() * 10;
+
+        depthStep += delta;
+
+        const value = depthStep % 5;
+
+        depth.value = value;
+    }
+
+    render();
+    stats.update();
+}
+
+function render() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_textures_anisotropy.ts b/examples-testing/examples/webgpu_textures_anisotropy.ts
new file mode 100644
index 000000000..7eb0ce1b3
--- /dev/null
+++ b/examples-testing/examples/webgpu_textures_anisotropy.ts
@@ -0,0 +1,155 @@
+import * as THREE from 'three';
+
+import Stats from 'three/addons/libs/stats.module.js';
+
+let container, stats;
+
+let camera, scene1, scene2, renderer;
+
+let mouseX = 0,
+    mouseY = 0;
+
+init();
+
+function init() {
+    const SCREEN_WIDTH = window.innerWidth;
+    const SCREEN_HEIGHT = window.innerHeight;
+
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
+
+    // RENDERER
+
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+    renderer.setAnimationLoop(animate);
+    renderer.autoClear = false;
+
+    renderer.domElement.style.position = 'relative';
+    container.appendChild(renderer.domElement);
+
+    //
+
+    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 25000);
+    camera.position.z = 1500;
+
+    scene1 = new THREE.Scene();
+    scene1.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
+
+    scene2 = new THREE.Scene();
+    scene2.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
+
+    scene1.add(new THREE.AmbientLight(0xeef0ff, 3));
+    scene2.add(new THREE.AmbientLight(0xeef0ff, 3));
+
+    const light1 = new THREE.DirectionalLight(0xffffff, 6);
+    light1.position.set(1, 1, 1);
+    scene1.add(light1);
+
+    const light2 = new THREE.DirectionalLight(0xffffff, 6);
+    light2.position.set(1, 1, 1);
+    scene2.add(light2);
+
+    // GROUND
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const maxAnisotropy = renderer.getMaxAnisotropy();
+
+    const texture1 = textureLoader.load('textures/crate.gif');
+    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
+
+    texture1.colorSpace = THREE.SRGBColorSpace;
+    texture1.anisotropy = renderer.getMaxAnisotropy();
+    texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
+    texture1.repeat.set(512, 512);
+
+    const texture2 = textureLoader.load('textures/crate.gif');
+    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
+
+    texture2.colorSpace = THREE.SRGBColorSpace;
+    texture2.anisotropy = 1;
+    texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping;
+    texture2.repeat.set(512, 512);
+
+    if (maxAnisotropy > 0) {
+        document.getElementById('val_left').innerHTML = texture1.anisotropy;
+        document.getElementById('val_right').innerHTML = texture2.anisotropy;
+    } else {
+        document.getElementById('val_left').innerHTML = 'not supported';
+        document.getElementById('val_right').innerHTML = 'not supported';
+    }
+
+    //
+
+    const geometry = new THREE.PlaneGeometry(100, 100);
+
+    const mesh1 = new THREE.Mesh(geometry, material1);
+    mesh1.rotation.x = -Math.PI / 2;
+    mesh1.scale.set(1000, 1000, 1000);
+
+    const mesh2 = new THREE.Mesh(geometry, material2);
+    mesh2.rotation.x = -Math.PI / 2;
+    mesh2.scale.set(1000, 1000, 1000);
+
+    scene1.add(mesh1);
+    scene2.add(mesh2);
+
+    // STATS1
+
+    stats = new Stats();
+    container.appendChild(stats.dom);
+
+    document.addEventListener('mousemove', onDocumentMouseMove);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentMouseMove(event) {
+    const windowHalfX = window.innerWidth / 2;
+    const windowHalfY = window.innerHeight / 2;
+
+    mouseX = event.clientX - windowHalfX;
+    mouseY = event.clientY - windowHalfY;
+}
+
+function animate() {
+    render();
+    stats.update();
+}
+
+function render() {
+    const SCREEN_WIDTH = window.innerWidth;
+    const SCREEN_HEIGHT = window.innerHeight;
+
+    camera.position.x += (mouseX - camera.position.x) * 0.05;
+    camera.position.y = THREE.MathUtils.clamp(
+        camera.position.y + (-(mouseY - 200) - camera.position.y) * 0.05,
+        50,
+        1000,
+    );
+
+    camera.lookAt(scene1.position);
+    renderer.clear();
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene1, camera);
+
+    renderer.setScissorTest(true);
+
+    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
+    renderer.render(scene2, camera);
+
+    renderer.setScissorTest(false);
+}
diff --git a/examples-testing/examples/webgpu_textures_partialupdate.ts b/examples-testing/examples/webgpu_textures_partialupdate.ts
new file mode 100644
index 000000000..e8ebe87db
--- /dev/null
+++ b/examples-testing/examples/webgpu_textures_partialupdate.ts
@@ -0,0 +1,103 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer, clock, dataTexture, diffuseMap;
+
+let last = 0;
+const position = new THREE.Vector2();
+const color = new THREE.Color();
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
+    camera.position.z = 2;
+
+    scene = new THREE.Scene();
+
+    clock = new THREE.Clock();
+
+    const loader = new THREE.TextureLoader();
+    diffuseMap = loader.load('textures/carbon/Carbon.png', animate);
+    diffuseMap.colorSpace = THREE.SRGBColorSpace;
+    diffuseMap.minFilter = THREE.LinearFilter;
+    diffuseMap.generateMipmaps = false;
+
+    const geometry = new THREE.PlaneGeometry(2, 2);
+    const material = new THREE.MeshBasicMaterial({ map: diffuseMap });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    //
+
+    const width = 32;
+    const height = 32;
+
+    const data = new Uint8Array(width * height * 4);
+    dataTexture = new THREE.DataTexture(data, width, height);
+
+    //
+
+    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+
+    document.body.appendChild(renderer.domElement);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+async function animate() {
+    requestAnimationFrame(animate);
+
+    const elapsedTime = clock.getElapsedTime();
+
+    await renderer.renderAsync(scene, camera);
+
+    if (elapsedTime - last > 0.1) {
+        last = elapsedTime;
+
+        position.x = 32 * THREE.MathUtils.randInt(1, 16) - 32;
+        position.y = 32 * THREE.MathUtils.randInt(1, 16) - 32;
+
+        // generate new color data
+        updateDataTexture(dataTexture);
+
+        // perform copy from src to dest texture to a random position
+
+        renderer.copyTextureToTexture(dataTexture, diffuseMap, null, position);
+    }
+}
+
+function updateDataTexture(texture) {
+    const size = texture.image.width * texture.image.height;
+    const data = texture.image.data;
+
+    // generate a random color and update texture data
+
+    color.setHex(Math.random() * 0xffffff);
+
+    const r = Math.floor(color.r * 255);
+    const g = Math.floor(color.g * 255);
+    const b = Math.floor(color.b * 255);
+
+    for (let i = 0; i < size; i++) {
+        const stride = i * 4;
+
+        data[stride] = r;
+        data[stride + 1] = g;
+        data[stride + 2] = b;
+        data[stride + 3] = 1;
+    }
+
+    texture.needsUpdate = true;
+}
diff --git a/examples-testing/examples/webgpu_tsl_coffee_smoke.ts b/examples-testing/examples/webgpu_tsl_coffee_smoke.ts
new file mode 100644
index 000000000..5c2c8ea48
--- /dev/null
+++ b/examples-testing/examples/webgpu_tsl_coffee_smoke.ts
@@ -0,0 +1,145 @@
+import * as THREE from 'three';
+import {
+    mix,
+    mul,
+    positionLocal,
+    smoothstep,
+    texture,
+    timerLocal,
+    rotateUV,
+    Fn,
+    uv,
+    vec2,
+    vec3,
+    vec4,
+} from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+let camera, scene, renderer, controls;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(8, 10, 12);
+
+    scene = new THREE.Scene();
+
+    // Loaders
+
+    const gltfLoader = new GLTFLoader();
+    const textureLoader = new THREE.TextureLoader();
+
+    // baked model
+
+    gltfLoader.load('./models/gltf/coffeeMug.glb', gltf => {
+        gltf.scene.getObjectByName('baked').material.map.anisotropy = 8;
+        scene.add(gltf.scene);
+    });
+
+    // geometry
+
+    const smokeGeometry = new THREE.PlaneGeometry(1, 1, 16, 64);
+    smokeGeometry.translate(0, 0.5, 0);
+    smokeGeometry.scale(1.5, 6, 1.5);
+
+    // texture
+
+    const noiseTexture = textureLoader.load('./textures/noises/perlin/128x128.png');
+    noiseTexture.wrapS = THREE.RepeatWrapping;
+    noiseTexture.wrapT = THREE.RepeatWrapping;
+
+    // material
+
+    const smokeMaterial = new THREE.MeshBasicNodeMaterial({
+        transparent: true,
+        side: THREE.DoubleSide,
+        depthWrite: false,
+    });
+    const time = timerLocal();
+
+    // position
+
+    smokeMaterial.positionNode = Fn(() => {
+        // twist
+
+        const twistNoiseUv = vec2(0.5, uv().y.mul(0.2).sub(time.mul(0.005)).mod(1));
+        const twist = texture(noiseTexture, twistNoiseUv).r.mul(10);
+        positionLocal.xz.assign(rotateUV(positionLocal.xz, twist, vec2(0)));
+
+        // wind
+
+        const windOffset = vec2(
+            texture(noiseTexture, vec2(0.25, time.mul(0.01)).mod(1)).r.sub(0.5),
+            texture(noiseTexture, vec2(0.75, time.mul(0.01)).mod(1)).r.sub(0.5),
+        ).mul(uv().y.pow(2).mul(10));
+        positionLocal.addAssign(windOffset);
+
+        return positionLocal;
+    })();
+
+    // color
+
+    smokeMaterial.colorNode = Fn(() => {
+        // alpha
+
+        const alphaNoiseUv = uv()
+            .mul(vec2(0.5, 0.3))
+            .add(vec2(0, time.mul(0.03).negate()));
+        const alpha = mul(
+            // pattern
+            texture(noiseTexture, alphaNoiseUv).r.smoothstep(0.4, 1),
+
+            // edges fade
+            smoothstep(0, 0.1, uv().x),
+            smoothstep(1, 0.9, uv().x),
+            smoothstep(0, 0.1, uv().y),
+            smoothstep(1, 0.9, uv().y),
+        );
+
+        // color
+
+        const finalColor = mix(vec3(0.6, 0.3, 0.2), vec3(1, 1, 1), alpha.pow(3));
+
+        return vec4(finalColor, alpha);
+    })();
+
+    // mesh
+
+    const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
+    smoke.position.y = 1.83;
+    scene.add(smoke);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // controls
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 0.1;
+    controls.maxDistance = 50;
+    controls.target.y = 3;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+async function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_tsl_vfx_flames.ts b/examples-testing/examples/webgpu_tsl_vfx_flames.ts
new file mode 100644
index 000000000..a9110fa8c
--- /dev/null
+++ b/examples-testing/examples/webgpu_tsl_vfx_flames.ts
@@ -0,0 +1,203 @@
+import * as THREE from 'three';
+import {
+    PI2,
+    spherizeUV,
+    sin,
+    step,
+    texture,
+    timerLocal,
+    Fn,
+    uv,
+    vec2,
+    vec3,
+    vec4,
+    mix,
+    billboarding,
+} from 'three/tsl';
+
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+let camera, scene, renderer, controls;
+
+init();
+
+function init() {
+    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100);
+    camera.position.set(1, 1, 3);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x201919);
+
+    // textures
+
+    const textureLoader = new THREE.TextureLoader();
+
+    const cellularTexture = textureLoader.load('./textures/noises/voronoi/grayscale-256x256.png');
+    const perlinTexture = textureLoader.load('./textures/noises/perlin/rgb-256x256.png');
+
+    // gradient canvas
+
+    const gradient = {};
+    gradient.element = document.createElement('canvas');
+    gradient.element.width = 128;
+    gradient.element.height = 1;
+    gradient.context = gradient.element.getContext('2d');
+
+    gradient.colors = ['#090033', '#5f1f93', '#e02e96', '#ffbd80', '#fff0db'];
+
+    gradient.texture = new THREE.CanvasTexture(gradient.element);
+    gradient.texture.colorSpace = THREE.SRGBColorSpace;
+
+    gradient.update = () => {
+        const fillGradient = gradient.context.createLinearGradient(0, 0, gradient.element.width, 0);
+
+        for (let i = 0; i < gradient.colors.length; i++) {
+            const progress = i / (gradient.colors.length - 1);
+            const color = gradient.colors[i];
+            fillGradient.addColorStop(progress, color);
+        }
+
+        gradient.context.fillStyle = fillGradient;
+        gradient.context.fillRect(0, 0, gradient.element.width, gradient.element.height);
+
+        gradient.texture.needsUpdate = true;
+    };
+
+    gradient.update();
+
+    // flame 1 material
+
+    const flame1Material = new THREE.SpriteNodeMaterial({ transparent: true, side: THREE.DoubleSide });
+
+    flame1Material.colorNode = Fn(() => {
+        const time = timerLocal();
+
+        // main UV
+        const mainUv = uv().toVar();
+        mainUv.assign(spherizeUV(mainUv, 10).mul(0.6).add(0.2)); // spherize
+        mainUv.assign(mainUv.pow(vec2(1, 2))); // stretch
+        mainUv.assign(mainUv.mul(2, 1).sub(vec2(0.5, 0))); // scale
+
+        // gradients
+        const gradient1 = sin(time.mul(10).sub(mainUv.y.mul(PI2).mul(2))).toVar();
+        const gradient2 = mainUv.y.smoothstep(0, 1).toVar();
+        mainUv.x.addAssign(gradient1.mul(gradient2).mul(0.2));
+
+        // cellular noise
+        const cellularUv = mainUv
+            .mul(0.5)
+            .add(vec2(0, time.negate().mul(0.5)))
+            .mod(1);
+        const cellularNoise = texture(cellularTexture, cellularUv, 0).r.oneMinus().smoothstep(0, 0.5).oneMinus();
+        cellularNoise.mulAssign(gradient2);
+
+        // shape
+        const shape = mainUv.sub(0.5).mul(vec2(3, 2)).length().oneMinus().toVar();
+        shape.assign(shape.sub(cellularNoise));
+
+        // gradient color
+        const gradientColor = texture(gradient.texture, vec2(shape.remap(0, 1, 0, 1), 0));
+
+        // output
+        const color = mix(gradientColor, vec3(1), shape.step(0.8).oneMinus());
+        const alpha = shape.smoothstep(0, 0.3);
+        return vec4(color.rgb, alpha);
+    })();
+
+    // flame 2 material
+
+    const flame2Material = new THREE.SpriteNodeMaterial({ transparent: true, side: THREE.DoubleSide });
+
+    flame2Material.colorNode = Fn(() => {
+        const time = timerLocal();
+
+        // main UV
+        const mainUv = uv().toVar();
+        mainUv.assign(spherizeUV(mainUv, 10).mul(0.6).add(0.2)); // spherize
+        mainUv.assign(mainUv.pow(vec2(1, 3))); // stretch
+        mainUv.assign(mainUv.mul(2, 1).sub(vec2(0.5, 0))); // scale
+
+        // perlin noise
+        const perlinUv = mainUv.add(vec2(0, time.negate().mul(1))).mod(1);
+        const perlinNoise = texture(perlinTexture, perlinUv, 0).sub(0.5).mul(1);
+        mainUv.x.addAssign(perlinNoise.x.mul(0.5));
+
+        // gradients
+        const gradient1 = sin(time.mul(10).sub(mainUv.y.mul(PI2).mul(2)));
+        const gradient2 = mainUv.y.smoothstep(0, 1);
+        const gradient3 = mainUv.y.smoothstep(1, 0.7);
+        mainUv.x.addAssign(gradient1.mul(gradient2).mul(0.2));
+
+        // displaced perlin noise
+        const displacementPerlinUv = mainUv
+            .mul(0.5)
+            .add(vec2(0, time.negate().mul(0.25)))
+            .mod(1);
+        const displacementPerlinNoise = texture(perlinTexture, displacementPerlinUv, 0).sub(0.5).mul(1);
+        const displacedPerlinUv = mainUv
+            .add(vec2(0, time.negate().mul(0.5)))
+            .add(displacementPerlinNoise)
+            .mod(1);
+        const displacedPerlinNoise = texture(perlinTexture, displacedPerlinUv, 0).sub(0.5).mul(1);
+        mainUv.x.addAssign(displacedPerlinNoise.mul(0.5));
+
+        // cellular noise
+        const cellularUv = mainUv.add(vec2(0, time.negate().mul(1.5))).mod(1);
+        const cellularNoise = texture(cellularTexture, cellularUv, 0).r.oneMinus().smoothstep(0.25, 1);
+
+        // shape
+        const shape = mainUv.sub(0.5).mul(vec2(6, 1)).length().step(0.5);
+        shape.assign(shape.mul(cellularNoise));
+        shape.mulAssign(gradient3);
+        shape.assign(step(0.01, shape));
+
+        // output
+        return vec4(vec3(1), shape);
+    })();
+
+    // billboarding - follow the camera rotation only horizontally
+
+    flame1Material.vertexNode = billboarding();
+    flame2Material.vertexNode = billboarding();
+
+    // meshes
+
+    const flame1 = new THREE.Sprite(flame1Material);
+    flame1.center.set(0.5, 0);
+    flame1.scale.x = 0.5; // optional
+    flame1.position.x = -0.5;
+    scene.add(flame1);
+
+    const flame2 = new THREE.Sprite(flame2Material);
+    flame2.center.set(0.5, 0);
+    flame2.position.x = 0.5;
+    scene.add(flame2);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    controls = new OrbitControls(camera, renderer.domElement);
+    controls.enableDamping = true;
+    controls.minDistance = 0.1;
+    controls.maxDistance = 50;
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+async function animate() {
+    controls.update();
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_video_panorama.ts b/examples-testing/examples/webgpu_video_panorama.ts
new file mode 100644
index 000000000..e409b3c07
--- /dev/null
+++ b/examples-testing/examples/webgpu_video_panorama.ts
@@ -0,0 +1,99 @@
+import * as THREE from 'three';
+
+let camera, scene, renderer;
+
+let isUserInteracting = false,
+    lon = 0,
+    lat = 0,
+    phi = 0,
+    theta = 0,
+    onPointerDownPointerX = 0,
+    onPointerDownPointerY = 0,
+    onPointerDownLon = 0,
+    onPointerDownLat = 0;
+
+const distance = 0.5;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.25, 10);
+
+    scene = new THREE.Scene();
+
+    const geometry = new THREE.SphereGeometry(5, 60, 40);
+    // invert the geometry on the x-axis so that all of the faces point inward
+    geometry.scale(-1, 1, 1);
+
+    const video = document.getElementById('video');
+    video.play();
+
+    const texture = new THREE.VideoTexture(video);
+    texture.colorSpace = THREE.SRGBColorSpace;
+    const material = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh = new THREE.Mesh(geometry, material);
+    scene.add(mesh);
+
+    renderer = new THREE.WebGPURenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    container.appendChild(renderer.domElement);
+
+    document.addEventListener('pointerdown', onPointerDown);
+    document.addEventListener('pointermove', onPointerMove);
+    document.addEventListener('pointerup', onPointerUp);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onPointerDown(event) {
+    isUserInteracting = true;
+
+    onPointerDownPointerX = event.clientX;
+    onPointerDownPointerY = event.clientY;
+
+    onPointerDownLon = lon;
+    onPointerDownLat = lat;
+}
+
+function onPointerMove(event) {
+    if (isUserInteracting === true) {
+        lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;
+        lat = (onPointerDownPointerY - event.clientY) * 0.1 + onPointerDownLat;
+    }
+}
+
+function onPointerUp() {
+    isUserInteracting = false;
+}
+
+function animate() {
+    update();
+}
+
+function update() {
+    lat = Math.max(-85, Math.min(85, lat));
+    phi = THREE.MathUtils.degToRad(90 - lat);
+    theta = THREE.MathUtils.degToRad(lon);
+
+    camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
+    camera.position.y = distance * Math.cos(phi);
+    camera.position.z = distance * Math.sin(phi) * Math.sin(theta);
+
+    camera.lookAt(0, 0, 0);
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webgpu_water.ts b/examples-testing/examples/webgpu_water.ts
new file mode 100644
index 000000000..76e09f1f8
--- /dev/null
+++ b/examples-testing/examples/webgpu_water.ts
@@ -0,0 +1,171 @@
+import * as THREE from 'three';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { WaterMesh } from 'three/addons/objects/Water2Mesh.js';
+
+let scene, camera, clock, renderer, water;
+
+let torusKnot;
+
+const params = {
+    color: '#ffffff',
+    scale: 4,
+    flowX: 1,
+    flowY: 1,
+};
+
+init();
+
+function init() {
+    // scene
+
+    scene = new THREE.Scene();
+
+    // camera
+
+    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
+    camera.position.set(-15, 7, 15);
+    camera.lookAt(scene.position);
+
+    // clock
+
+    clock = new THREE.Clock();
+
+    // mesh
+
+    const torusKnotGeometry = new THREE.TorusKnotGeometry(3, 1, 256, 32);
+    const torusKnotMaterial = new THREE.MeshNormalMaterial();
+
+    torusKnot = new THREE.Mesh(torusKnotGeometry, torusKnotMaterial);
+    torusKnot.position.y = 4;
+    torusKnot.scale.set(0.5, 0.5, 0.5);
+    scene.add(torusKnot);
+
+    // ground
+
+    const groundGeometry = new THREE.PlaneGeometry(20, 20);
+    const groundMaterial = new THREE.MeshStandardMaterial({ roughness: 0.8, metalness: 0.4 });
+    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
+    ground.rotation.x = Math.PI * -0.5;
+    scene.add(ground);
+
+    const textureLoader = new THREE.TextureLoader();
+    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
+        map.wrapS = THREE.RepeatWrapping;
+        map.wrapT = THREE.RepeatWrapping;
+        map.anisotropy = 16;
+        map.repeat.set(4, 4);
+        map.colorSpace = THREE.SRGBColorSpace;
+        groundMaterial.map = map;
+        groundMaterial.needsUpdate = true;
+    });
+
+    //
+
+    const normalMap0 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
+    const normalMap1 = textureLoader.load('textures/water/Water_2_M_Normal.jpg');
+
+    normalMap0.wrapS = normalMap0.wrapT = THREE.RepeatWrapping;
+    normalMap1.wrapS = normalMap1.wrapT = THREE.RepeatWrapping;
+
+    // water
+
+    const waterGeometry = new THREE.PlaneGeometry(20, 20);
+
+    water = new WaterMesh(waterGeometry, {
+        color: params.color,
+        scale: params.scale,
+        flowDirection: new THREE.Vector2(params.flowX, params.flowY),
+        normalMap0: normalMap0,
+        normalMap1: normalMap1,
+    });
+
+    water.position.y = 1;
+    water.rotation.x = Math.PI * -0.5;
+    scene.add(water);
+
+    // skybox
+
+    const cubeTextureLoader = new THREE.CubeTextureLoader();
+    cubeTextureLoader.setPath('textures/cube/Park2/');
+
+    const cubeTexture = cubeTextureLoader.load([
+        'posx.jpg',
+        'negx.jpg',
+        'posy.jpg',
+        'negy.jpg',
+        'posz.jpg',
+        'negz.jpg',
+    ]);
+
+    scene.background = cubeTexture;
+
+    // light
+
+    const ambientLight = new THREE.AmbientLight(0xe7e7e7, 1.2);
+    scene.add(ambientLight);
+
+    const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
+    directionalLight.position.set(-1, 1, 1);
+    scene.add(directionalLight);
+
+    // renderer
+
+    renderer = new THREE.WebGPURenderer();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setAnimationLoop(animate);
+    document.body.appendChild(renderer.domElement);
+
+    // gui
+
+    const gui = new GUI();
+    const waterNode = water.material.fragmentNode;
+
+    gui.addColor(params, 'color').onChange(function (value) {
+        waterNode.color.value.set(value);
+    });
+    gui.add(params, 'scale', 1, 10).onChange(function (value) {
+        waterNode.scale.value = value;
+    });
+    gui.add(params, 'flowX', -1, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            waterNode.flowDirection.value.x = value;
+            waterNode.flowDirection.value.normalize();
+        });
+    gui.add(params, 'flowY', -1, 1)
+        .step(0.01)
+        .onChange(function (value) {
+            waterNode.flowDirection.value.y = value;
+            waterNode.flowDirection.value.normalize();
+        });
+
+    gui.open();
+
+    //
+
+    const controls = new OrbitControls(camera, renderer.domElement);
+    controls.minDistance = 5;
+    controls.maxDistance = 50;
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const delta = clock.getDelta();
+
+    torusKnot.rotation.x += delta;
+    torusKnot.rotation.y += delta * 0.5;
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_ar_cones.ts b/examples-testing/examples/webxr_ar_cones.ts
new file mode 100644
index 000000000..95eb34393
--- /dev/null
+++ b/examples-testing/examples/webxr_ar_cones.ts
@@ -0,0 +1,66 @@
+import * as THREE from 'three';
+import { ARButton } from 'three/addons/webxr/ARButton.js';
+
+let camera, scene, renderer;
+let controller;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
+
+    const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
+    light.position.set(0.5, 1, 0.25);
+    scene.add(light);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    document.body.appendChild(ARButton.createButton(renderer));
+
+    //
+
+    const geometry = new THREE.CylinderGeometry(0, 0.05, 0.2, 32).rotateX(Math.PI / 2);
+
+    function onSelect() {
+        const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random() });
+        const mesh = new THREE.Mesh(geometry, material);
+        mesh.position.set(0, 0, -0.3).applyMatrix4(controller.matrixWorld);
+        mesh.quaternion.setFromRotationMatrix(controller.matrixWorld);
+        scene.add(mesh);
+    }
+
+    controller = renderer.xr.getController(0);
+    controller.addEventListener('select', onSelect);
+    scene.add(controller);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_ar_hittest.ts b/examples-testing/examples/webxr_ar_hittest.ts
new file mode 100644
index 000000000..1867cc470
--- /dev/null
+++ b/examples-testing/examples/webxr_ar_hittest.ts
@@ -0,0 +1,115 @@
+import * as THREE from 'three';
+import { ARButton } from 'three/addons/webxr/ARButton.js';
+
+let container;
+let camera, scene, renderer;
+let controller;
+
+let reticle;
+
+let hitTestSource = null;
+let hitTestSourceRequested = false;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
+
+    const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
+    light.position.set(0.5, 1, 0.25);
+    scene.add(light);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    //
+
+    document.body.appendChild(ARButton.createButton(renderer, { requiredFeatures: ['hit-test'] }));
+
+    //
+
+    const geometry = new THREE.CylinderGeometry(0.1, 0.1, 0.2, 32).translate(0, 0.1, 0);
+
+    function onSelect() {
+        if (reticle.visible) {
+            const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random() });
+            const mesh = new THREE.Mesh(geometry, material);
+            reticle.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
+            mesh.scale.y = Math.random() * 2 + 1;
+            scene.add(mesh);
+        }
+    }
+
+    controller = renderer.xr.getController(0);
+    controller.addEventListener('select', onSelect);
+    scene.add(controller);
+
+    reticle = new THREE.Mesh(
+        new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2),
+        new THREE.MeshBasicMaterial(),
+    );
+    reticle.matrixAutoUpdate = false;
+    reticle.visible = false;
+    scene.add(reticle);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate(timestamp, frame) {
+    if (frame) {
+        const referenceSpace = renderer.xr.getReferenceSpace();
+        const session = renderer.xr.getSession();
+
+        if (hitTestSourceRequested === false) {
+            session.requestReferenceSpace('viewer').then(function (referenceSpace) {
+                session.requestHitTestSource({ space: referenceSpace }).then(function (source) {
+                    hitTestSource = source;
+                });
+            });
+
+            session.addEventListener('end', function () {
+                hitTestSourceRequested = false;
+                hitTestSource = null;
+            });
+
+            hitTestSourceRequested = true;
+        }
+
+        if (hitTestSource) {
+            const hitTestResults = frame.getHitTestResults(hitTestSource);
+
+            if (hitTestResults.length) {
+                const hit = hitTestResults[0];
+
+                reticle.visible = true;
+                reticle.matrix.fromArray(hit.getPose(referenceSpace).transform.matrix);
+            } else {
+                reticle.visible = false;
+            }
+        }
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_ar_lighting.ts b/examples-testing/examples/webxr_ar_lighting.ts
new file mode 100644
index 000000000..9de23ad94
--- /dev/null
+++ b/examples-testing/examples/webxr_ar_lighting.ts
@@ -0,0 +1,124 @@
+import * as THREE from 'three';
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { ARButton } from 'three/addons/webxr/ARButton.js';
+import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js';
+
+let camera, scene, renderer;
+let controller;
+let defaultEnvironment;
+
+init();
+
+function init() {
+    const container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
+
+    const defaultLight = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
+    defaultLight.position.set(0.5, 1, 0.25);
+    scene.add(defaultLight);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    // Don't add the XREstimatedLight to the scene initially.
+    // It doesn't have any estimated lighting values until an AR session starts.
+
+    const xrLight = new XREstimatedLight(renderer);
+
+    xrLight.addEventListener('estimationstart', () => {
+        // Swap the default light out for the estimated one one we start getting some estimated values.
+        scene.add(xrLight);
+        scene.remove(defaultLight);
+
+        // The estimated lighting also provides an environment cubemap, which we can apply here.
+        if (xrLight.environment) {
+            scene.environment = xrLight.environment;
+        }
+    });
+
+    xrLight.addEventListener('estimationend', () => {
+        // Swap the lights back when we stop receiving estimated values.
+        scene.add(defaultLight);
+        scene.remove(xrLight);
+
+        // Revert back to the default environment.
+        scene.environment = defaultEnvironment;
+    });
+
+    //
+
+    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        defaultEnvironment = texture;
+
+        scene.environment = defaultEnvironment;
+    });
+
+    //
+
+    // In order for lighting estimation to work, 'light-estimation' must be included as either an optional or required feature.
+    document.body.appendChild(ARButton.createButton(renderer, { optionalFeatures: ['light-estimation'] }));
+
+    //
+
+    const ballGeometry = new THREE.SphereGeometry(0.175, 32, 32);
+    const ballGroup = new THREE.Group();
+    ballGroup.position.z = -2;
+
+    const rows = 3;
+    const cols = 3;
+
+    for (let i = 0; i < rows; i++) {
+        for (let j = 0; j < cols; j++) {
+            const ballMaterial = new THREE.MeshStandardMaterial({
+                color: 0xdddddd,
+                roughness: i / rows,
+                metalness: j / cols,
+            });
+            const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
+            ballMesh.position.set((i + 0.5 - rows * 0.5) * 0.4, (j + 0.5 - cols * 0.5) * 0.4, 0);
+            ballGroup.add(ballMesh);
+        }
+    }
+
+    scene.add(ballGroup);
+
+    //
+
+    function onSelect() {
+        ballGroup.position.set(0, 0, -2).applyMatrix4(controller.matrixWorld);
+        ballGroup.quaternion.setFromRotationMatrix(controller.matrixWorld);
+    }
+
+    controller = renderer.xr.getController(0);
+    controller.addEventListener('select', onSelect);
+    scene.add(controller);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_ar_plane_detection.ts b/examples-testing/examples/webxr_ar_plane_detection.ts
new file mode 100644
index 000000000..841b6b04b
--- /dev/null
+++ b/examples-testing/examples/webxr_ar_plane_detection.ts
@@ -0,0 +1,46 @@
+import * as THREE from 'three';
+import { ARButton } from 'three/addons/webxr/ARButton.js';
+import { XRPlanes } from 'three/addons/webxr/XRPlanes.js';
+
+//
+
+const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setAnimationLoop(animate);
+renderer.xr.enabled = true;
+document.body.appendChild(renderer.domElement);
+
+document.body.appendChild(
+    ARButton.createButton(renderer, {
+        requiredFeatures: ['plane-detection'],
+    }),
+);
+
+window.addEventListener('resize', onWindowResize);
+
+//
+
+const scene = new THREE.Scene();
+
+const planes = new XRPlanes(renderer);
+scene.add(planes);
+
+const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
+
+const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
+light.position.set(0.5, 1, 0.25);
+scene.add(light);
+
+//
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_vr_handinput.ts b/examples-testing/examples/webxr_vr_handinput.ts
new file mode 100644
index 000000000..d746e4582
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_handinput.ts
@@ -0,0 +1,126 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
+import { XRHandModelFactory } from 'three/addons/webxr/XRHandModelFactory.js';
+
+let container;
+let camera, scene, renderer;
+let hand1, hand2;
+let controller1, controller2;
+let controllerGrip1, controllerGrip2;
+
+let controls;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x444444);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
+    camera.position.set(0, 1.6, 3);
+
+    controls = new OrbitControls(camera, container);
+    controls.target.set(0, 1.6, 0);
+    controls.update();
+
+    const floorGeometry = new THREE.PlaneGeometry(4, 4);
+    const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
+    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
+    floor.rotation.x = -Math.PI / 2;
+    floor.receiveShadow = true;
+    scene.add(floor);
+
+    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 6, 0);
+    light.castShadow = true;
+    light.shadow.camera.top = 2;
+    light.shadow.camera.bottom = -2;
+    light.shadow.camera.right = 2;
+    light.shadow.camera.left = -2;
+    light.shadow.mapSize.set(4096, 4096);
+    scene.add(light);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.xr.enabled = true;
+
+    container.appendChild(renderer.domElement);
+
+    const sessionInit = {
+        requiredFeatures: ['hand-tracking'],
+    };
+
+    document.body.appendChild(VRButton.createButton(renderer, sessionInit));
+
+    // controllers
+
+    controller1 = renderer.xr.getController(0);
+    scene.add(controller1);
+
+    controller2 = renderer.xr.getController(1);
+    scene.add(controller2);
+
+    const controllerModelFactory = new XRControllerModelFactory();
+    const handModelFactory = new XRHandModelFactory();
+
+    // Hand 1
+    controllerGrip1 = renderer.xr.getControllerGrip(0);
+    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
+    scene.add(controllerGrip1);
+
+    hand1 = renderer.xr.getHand(0);
+    hand1.add(handModelFactory.createHandModel(hand1));
+
+    scene.add(hand1);
+
+    // Hand 2
+    controllerGrip2 = renderer.xr.getControllerGrip(1);
+    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
+    scene.add(controllerGrip2);
+
+    hand2 = renderer.xr.getHand(1);
+    hand2.add(handModelFactory.createHandModel(hand2));
+    scene.add(hand2);
+
+    //
+
+    const geometry = new THREE.BufferGeometry().setFromPoints([
+        new THREE.Vector3(0, 0, 0),
+        new THREE.Vector3(0, 0, -1),
+    ]);
+
+    const line = new THREE.Line(geometry);
+    line.name = 'line';
+    line.scale.z = 5;
+
+    controller1.add(line.clone());
+    controller2.add(line.clone());
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+//
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_vr_panorama.ts b/examples-testing/examples/webxr_vr_panorama.ts
new file mode 100644
index 000000000..535e1c937
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_panorama.ts
@@ -0,0 +1,92 @@
+import * as THREE from 'three';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+let camera;
+let renderer;
+let scene;
+
+init();
+
+function init() {
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    renderer.xr.setReferenceSpaceType('local');
+    document.body.appendChild(renderer.domElement);
+
+    document.body.appendChild(VRButton.createButton(renderer));
+
+    //
+
+    scene = new THREE.Scene();
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
+    camera.layers.enable(1);
+
+    const geometry = new THREE.BoxGeometry(100, 100, 100);
+    geometry.scale(1, 1, -1);
+
+    const textures = getTexturesFromAtlasFile('textures/cube/sun_temple_stripe_stereo.jpg', 12);
+
+    const materials = [];
+
+    for (let i = 0; i < 6; i++) {
+        materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
+    }
+
+    const skyBox = new THREE.Mesh(geometry, materials);
+    skyBox.layers.set(1);
+    scene.add(skyBox);
+
+    const materialsR = [];
+
+    for (let i = 6; i < 12; i++) {
+        materialsR.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
+    }
+
+    const skyBoxR = new THREE.Mesh(geometry, materialsR);
+    skyBoxR.layers.set(2);
+    scene.add(skyBoxR);
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
+    const textures = [];
+
+    for (let i = 0; i < tilesNum; i++) {
+        textures[i] = new THREE.Texture();
+    }
+
+    const loader = new THREE.ImageLoader();
+    loader.load(atlasImgUrl, function (imageObj) {
+        let canvas, context;
+        const tileWidth = imageObj.height;
+
+        for (let i = 0; i < textures.length; i++) {
+            canvas = document.createElement('canvas');
+            context = canvas.getContext('2d');
+            canvas.height = tileWidth;
+            canvas.width = tileWidth;
+            context.drawImage(imageObj, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
+            textures[i].colorSpace = THREE.SRGBColorSpace;
+            textures[i].image = canvas;
+            textures[i].needsUpdate = true;
+        }
+    });
+
+    return textures;
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_vr_panorama_depth.ts b/examples-testing/examples/webxr_vr_panorama_depth.ts
new file mode 100644
index 000000000..66215469d
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_panorama_depth.ts
@@ -0,0 +1,90 @@
+import * as THREE from 'three';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+let camera, scene, renderer, sphere, clock;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+
+    clock = new THREE.Clock();
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x101010);
+
+    const light = new THREE.AmbientLight(0xffffff, 3);
+    scene.add(light);
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 2000);
+    scene.add(camera);
+
+    // Create the panoramic sphere geometery
+    const panoSphereGeo = new THREE.SphereGeometry(6, 256, 256);
+
+    // Create the panoramic sphere material
+    const panoSphereMat = new THREE.MeshStandardMaterial({
+        side: THREE.BackSide,
+        displacementScale: -4.0,
+    });
+
+    // Create the panoramic sphere mesh
+    sphere = new THREE.Mesh(panoSphereGeo, panoSphereMat);
+
+    // Load and assign the texture and depth map
+    const manager = new THREE.LoadingManager();
+    const loader = new THREE.TextureLoader(manager);
+
+    loader.load('./textures/kandao3.jpg', function (texture) {
+        texture.colorSpace = THREE.SRGBColorSpace;
+        texture.minFilter = THREE.NearestFilter;
+        texture.generateMipmaps = false;
+        sphere.material.map = texture;
+    });
+
+    loader.load('./textures/kandao3_depthmap.jpg', function (depth) {
+        depth.minFilter = THREE.NearestFilter;
+        depth.generateMipmaps = false;
+        sphere.material.displacementMap = depth;
+    });
+
+    // On load complete add the panoramic sphere to the scene
+    manager.onLoad = function () {
+        scene.add(sphere);
+    };
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    renderer.xr.setReferenceSpaceType('local');
+    container.appendChild(renderer.domElement);
+
+    document.body.appendChild(VRButton.createButton(renderer));
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    // If we are not presenting move the camera a little so the effect is visible
+
+    if (renderer.xr.isPresenting === false) {
+        const time = clock.getElapsedTime();
+
+        sphere.rotation.y += 0.001;
+        sphere.position.x = Math.sin(time) * 0.2;
+        sphere.position.z = Math.cos(time) * 0.2;
+    }
+
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_vr_rollercoaster.ts b/examples-testing/examples/webxr_vr_rollercoaster.ts
new file mode 100644
index 000000000..b8c35a9e3
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_rollercoaster.ts
@@ -0,0 +1,211 @@
+import * as THREE from 'three';
+
+import {
+    RollerCoasterGeometry,
+    RollerCoasterShadowGeometry,
+    RollerCoasterLiftersGeometry,
+    TreesGeometry,
+    SkyGeometry,
+} from 'three/addons/misc/RollerCoaster.js';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+let mesh, material, geometry;
+
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setPixelRatio(window.devicePixelRatio);
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setAnimationLoop(animate);
+renderer.xr.enabled = true;
+renderer.xr.setReferenceSpaceType('local');
+document.body.appendChild(renderer.domElement);
+
+document.body.appendChild(VRButton.createButton(renderer));
+
+//
+
+const scene = new THREE.Scene();
+scene.background = new THREE.Color(0xf0f0ff);
+
+const light = new THREE.HemisphereLight(0xfff0f0, 0x60606, 3);
+light.position.set(1, 1, 1);
+scene.add(light);
+
+const train = new THREE.Object3D();
+scene.add(train);
+
+const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 500);
+train.add(camera);
+
+// environment
+
+geometry = new THREE.PlaneGeometry(500, 500, 15, 15);
+geometry.rotateX(-Math.PI / 2);
+
+const positions = geometry.attributes.position.array;
+const vertex = new THREE.Vector3();
+
+for (let i = 0; i < positions.length; i += 3) {
+    vertex.fromArray(positions, i);
+
+    vertex.x += Math.random() * 10 - 5;
+    vertex.z += Math.random() * 10 - 5;
+
+    const distance = vertex.distanceTo(scene.position) / 5 - 25;
+    vertex.y = Math.random() * Math.max(0, distance);
+
+    vertex.toArray(positions, i);
+}
+
+geometry.computeVertexNormals();
+
+material = new THREE.MeshLambertMaterial({
+    color: 0x407000,
+});
+
+mesh = new THREE.Mesh(geometry, material);
+scene.add(mesh);
+
+geometry = new TreesGeometry(mesh);
+material = new THREE.MeshBasicMaterial({
+    side: THREE.DoubleSide,
+    vertexColors: true,
+});
+mesh = new THREE.Mesh(geometry, material);
+scene.add(mesh);
+
+geometry = new SkyGeometry();
+material = new THREE.MeshBasicMaterial({ color: 0xffffff });
+mesh = new THREE.Mesh(geometry, material);
+scene.add(mesh);
+
+//
+
+const PI2 = Math.PI * 2;
+
+const curve = (function () {
+    const vector = new THREE.Vector3();
+    const vector2 = new THREE.Vector3();
+
+    return {
+        getPointAt: function (t) {
+            t = t * PI2;
+
+            const x = Math.sin(t * 3) * Math.cos(t * 4) * 50;
+            const y = Math.sin(t * 10) * 2 + Math.cos(t * 17) * 2 + 5;
+            const z = Math.sin(t) * Math.sin(t * 4) * 50;
+
+            return vector.set(x, y, z).multiplyScalar(2);
+        },
+
+        getTangentAt: function (t) {
+            const delta = 0.0001;
+            const t1 = Math.max(0, t - delta);
+            const t2 = Math.min(1, t + delta);
+
+            return vector2.copy(this.getPointAt(t2)).sub(this.getPointAt(t1)).normalize();
+        },
+    };
+})();
+
+geometry = new RollerCoasterGeometry(curve, 1500);
+material = new THREE.MeshPhongMaterial({
+    vertexColors: true,
+});
+mesh = new THREE.Mesh(geometry, material);
+scene.add(mesh);
+
+geometry = new RollerCoasterLiftersGeometry(curve, 100);
+material = new THREE.MeshPhongMaterial();
+mesh = new THREE.Mesh(geometry, material);
+mesh.position.y = 0.1;
+scene.add(mesh);
+
+geometry = new RollerCoasterShadowGeometry(curve, 500);
+material = new THREE.MeshBasicMaterial({
+    color: 0x305000,
+    depthWrite: false,
+    transparent: true,
+});
+mesh = new THREE.Mesh(geometry, material);
+mesh.position.y = 0.1;
+scene.add(mesh);
+
+const funfairs = [];
+
+//
+
+geometry = new THREE.CylinderGeometry(10, 10, 5, 15);
+material = new THREE.MeshLambertMaterial({
+    color: 0xff8080,
+});
+mesh = new THREE.Mesh(geometry, material);
+mesh.position.set(-80, 10, -70);
+mesh.rotation.x = Math.PI / 2;
+scene.add(mesh);
+
+funfairs.push(mesh);
+
+geometry = new THREE.CylinderGeometry(5, 6, 4, 10);
+material = new THREE.MeshLambertMaterial({
+    color: 0x8080ff,
+});
+mesh = new THREE.Mesh(geometry, material);
+mesh.position.set(50, 2, 30);
+scene.add(mesh);
+
+funfairs.push(mesh);
+
+//
+
+window.addEventListener('resize', onWindowResize);
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+const position = new THREE.Vector3();
+const tangent = new THREE.Vector3();
+
+const lookAt = new THREE.Vector3();
+
+let velocity = 0;
+let progress = 0;
+
+let prevTime = performance.now();
+
+function animate() {
+    const time = performance.now();
+    const delta = time - prevTime;
+
+    for (let i = 0; i < funfairs.length; i++) {
+        funfairs[i].rotation.y = time * 0.0004;
+    }
+
+    //
+
+    progress += velocity;
+    progress = progress % 1;
+
+    position.copy(curve.getPointAt(progress));
+    position.y += 0.3;
+
+    train.position.copy(position);
+
+    tangent.copy(curve.getTangentAt(progress));
+
+    velocity -= tangent.y * 0.0000001 * delta;
+    velocity = Math.max(0.00004, Math.min(0.0002, velocity));
+
+    train.lookAt(lookAt.copy(position).sub(tangent));
+
+    //
+
+    renderer.render(scene, camera);
+
+    prevTime = time;
+}
diff --git a/examples-testing/examples/webxr_vr_sandbox.ts b/examples-testing/examples/webxr_vr_sandbox.ts
new file mode 100644
index 000000000..9e8e75909
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_sandbox.ts
@@ -0,0 +1,192 @@
+import * as THREE from 'three';
+
+import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+import { Reflector } from 'three/addons/objects/Reflector.js';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
+import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
+import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
+
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+import Stats from 'three/addons/libs/stats.module.js';
+
+let camera, scene, renderer;
+let reflector;
+let stats, statsMesh;
+
+const parameters = {
+    radius: 0.6,
+    tube: 0.2,
+    tubularSegments: 150,
+    radialSegments: 20,
+    p: 2,
+    q: 3,
+    thickness: 0.5,
+};
+
+init();
+
+function init() {
+    scene = new THREE.Scene();
+
+    new RGBELoader().setPath('textures/equirectangular/').load('moonless_golf_1k.hdr', function (texture) {
+        texture.mapping = THREE.EquirectangularReflectionMapping;
+
+        scene.background = texture;
+        scene.environment = texture;
+    });
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
+    camera.position.set(0, 1.6, 1.5);
+
+    //
+
+    const torusGeometry = new THREE.TorusKnotGeometry(...Object.values(parameters));
+    const torusMaterial = new THREE.MeshPhysicalMaterial({
+        transmission: 1.0,
+        roughness: 0,
+        metalness: 0.25,
+        thickness: 0.5,
+        side: THREE.DoubleSide,
+    });
+    const torus = new THREE.Mesh(torusGeometry, torusMaterial);
+    torus.name = 'torus';
+    torus.position.y = 1.5;
+    torus.position.z = -2;
+    scene.add(torus);
+
+    const cylinderGeometry = new THREE.CylinderGeometry(1, 1, 0.1, 50);
+    const cylinderMaterial = new THREE.MeshStandardMaterial();
+    const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
+    cylinder.position.z = -2;
+    scene.add(cylinder);
+
+    //
+
+    reflector = new Reflector(new THREE.PlaneGeometry(2, 2), {
+        textureWidth: window.innerWidth,
+        textureHeight: window.innerHeight,
+    });
+    reflector.position.x = 1;
+    reflector.position.y = 1.5;
+    reflector.position.z = -3;
+    reflector.rotation.y = -Math.PI / 4;
+    scene.add(reflector);
+
+    const frameGeometry = new THREE.BoxGeometry(2.1, 2.1, 0.1);
+    const frameMaterial = new THREE.MeshPhongMaterial();
+    const frame = new THREE.Mesh(frameGeometry, frameMaterial);
+    frame.position.z = -0.07;
+    reflector.add(frame);
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.autoClear = false;
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    renderer.toneMapping = THREE.ACESFilmicToneMapping;
+    renderer.toneMappingExposure = 1;
+    document.body.appendChild(renderer.domElement);
+
+    document.body.appendChild(VRButton.createButton(renderer));
+
+    window.addEventListener('resize', onWindowResize);
+
+    //
+
+    const geometry = new THREE.BufferGeometry();
+    geometry.setFromPoints([new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -5)]);
+
+    const controller1 = renderer.xr.getController(0);
+    controller1.add(new THREE.Line(geometry));
+    scene.add(controller1);
+
+    const controller2 = renderer.xr.getController(1);
+    controller2.add(new THREE.Line(geometry));
+    scene.add(controller2);
+
+    //
+
+    const controllerModelFactory = new XRControllerModelFactory();
+
+    const controllerGrip1 = renderer.xr.getControllerGrip(0);
+    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
+    scene.add(controllerGrip1);
+
+    const controllerGrip2 = renderer.xr.getControllerGrip(1);
+    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
+    scene.add(controllerGrip2);
+
+    // GUI
+
+    function onChange() {
+        torus.geometry.dispose();
+        torus.geometry = new THREE.TorusKnotGeometry(...Object.values(parameters));
+    }
+
+    function onThicknessChange() {
+        torus.material.thickness = parameters.thickness;
+    }
+
+    const gui = new GUI({ width: 300 });
+    gui.add(parameters, 'radius', 0.0, 1.0).onChange(onChange);
+    gui.add(parameters, 'tube', 0.0, 1.0).onChange(onChange);
+    gui.add(parameters, 'tubularSegments', 10, 150, 1).onChange(onChange);
+    gui.add(parameters, 'radialSegments', 2, 20, 1).onChange(onChange);
+    gui.add(parameters, 'p', 1, 10, 1).onChange(onChange);
+    gui.add(parameters, 'q', 0, 10, 1).onChange(onChange);
+    gui.add(parameters, 'thickness', 0, 1).onChange(onThicknessChange);
+    gui.domElement.style.visibility = 'hidden';
+
+    const group = new InteractiveGroup();
+    group.listenToPointerEvents(renderer, camera);
+    group.listenToXRControllerEvents(controller1);
+    group.listenToXRControllerEvents(controller2);
+    scene.add(group);
+
+    const mesh = new HTMLMesh(gui.domElement);
+    mesh.position.x = -0.75;
+    mesh.position.y = 1.5;
+    mesh.position.z = -0.5;
+    mesh.rotation.y = Math.PI / 4;
+    mesh.scale.setScalar(2);
+    group.add(mesh);
+
+    // Add stats.js
+    stats = new Stats();
+    stats.dom.style.width = '80px';
+    stats.dom.style.height = '48px';
+    document.body.appendChild(stats.dom);
+
+    statsMesh = new HTMLMesh(stats.dom);
+    statsMesh.position.x = -0.75;
+    statsMesh.position.y = 2;
+    statsMesh.position.z = -0.6;
+    statsMesh.rotation.y = Math.PI / 4;
+    statsMesh.scale.setScalar(2.5);
+    group.add(statsMesh);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    const time = performance.now() * 0.0002;
+    const torus = scene.getObjectByName('torus');
+    torus.rotation.x = time * 0.4;
+    torus.rotation.y = time;
+
+    renderer.render(scene, camera);
+    stats.update();
+
+    // Canvas elements doesn't trigger DOM updates, so we have to update the texture
+    statsMesh.material.map.update();
+}
diff --git a/examples-testing/examples/webxr_vr_video.ts b/examples-testing/examples/webxr_vr_video.ts
new file mode 100644
index 000000000..50a990412
--- /dev/null
+++ b/examples-testing/examples/webxr_vr_video.ts
@@ -0,0 +1,92 @@
+import * as THREE from 'three';
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+let camera, scene, renderer;
+
+init();
+
+function init() {
+    const container = document.getElementById('container');
+    container.addEventListener('click', function () {
+        video.play();
+    });
+
+    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 2000);
+    camera.layers.enable(1); // render left view when no stereo available
+
+    // video
+
+    const video = document.getElementById('video');
+    video.play();
+
+    const texture = new THREE.VideoTexture(video);
+    texture.colorSpace = THREE.SRGBColorSpace;
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x101010);
+
+    // left
+
+    const geometry1 = new THREE.SphereGeometry(500, 60, 40);
+    // invert the geometry on the x-axis so that all of the faces point inward
+    geometry1.scale(-1, 1, 1);
+
+    const uvs1 = geometry1.attributes.uv.array;
+
+    for (let i = 0; i < uvs1.length; i += 2) {
+        uvs1[i] *= 0.5;
+    }
+
+    const material1 = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh1 = new THREE.Mesh(geometry1, material1);
+    mesh1.rotation.y = -Math.PI / 2;
+    mesh1.layers.set(1); // display in left eye only
+    scene.add(mesh1);
+
+    // right
+
+    const geometry2 = new THREE.SphereGeometry(500, 60, 40);
+    geometry2.scale(-1, 1, 1);
+
+    const uvs2 = geometry2.attributes.uv.array;
+
+    for (let i = 0; i < uvs2.length; i += 2) {
+        uvs2[i] *= 0.5;
+        uvs2[i] += 0.5;
+    }
+
+    const material2 = new THREE.MeshBasicMaterial({ map: texture });
+
+    const mesh2 = new THREE.Mesh(geometry2, material2);
+    mesh2.rotation.y = -Math.PI / 2;
+    mesh2.layers.set(2); // display in right eye only
+    scene.add(mesh2);
+
+    //
+
+    renderer = new THREE.WebGLRenderer();
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.xr.enabled = true;
+    renderer.xr.setReferenceSpaceType('local');
+    container.appendChild(renderer.domElement);
+
+    document.body.appendChild(VRButton.createButton(renderer));
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_xr_controls_transform.ts b/examples-testing/examples/webxr_xr_controls_transform.ts
new file mode 100644
index 000000000..f3b4796e6
--- /dev/null
+++ b/examples-testing/examples/webxr_xr_controls_transform.ts
@@ -0,0 +1,210 @@
+import * as THREE from 'three';
+import { TransformControls } from 'three/addons/controls/TransformControls.js';
+import { XRButton } from 'three/addons/webxr/XRButton.js';
+import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
+
+let container;
+let camera, scene, renderer;
+let controller1, controller2, line;
+let controllerGrip1, controllerGrip2;
+
+let raycaster;
+
+let controls, group;
+
+init();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x808080);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
+    camera.position.set(0, 1.6, 0);
+
+    const floorGeometry = new THREE.PlaneGeometry(6, 6);
+    const floorMaterial = new THREE.ShadowMaterial({
+        opacity: 0.25,
+        blending: THREE.CustomBlending,
+        transparent: false,
+    });
+    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
+    floor.rotation.x = -Math.PI / 2;
+    floor.receiveShadow = true;
+    scene.add(floor);
+
+    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 6, 0);
+    light.castShadow = true;
+    light.shadow.camera.top = 3;
+    light.shadow.camera.bottom = -3;
+    light.shadow.camera.right = 3;
+    light.shadow.camera.left = -3;
+    light.shadow.mapSize.set(4096, 4096);
+    scene.add(light);
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const geometries = [
+        new THREE.BoxGeometry(0.2, 0.2, 0.2),
+        new THREE.ConeGeometry(0.2, 0.4, 64),
+        new THREE.CylinderGeometry(0.2, 0.2, 0.2, 64),
+        new THREE.IcosahedronGeometry(0.2, 8),
+        new THREE.TorusGeometry(0.2, 0.04, 64, 32),
+    ];
+
+    for (let i = 0; i < 16; i++) {
+        const geometry = geometries[Math.floor(Math.random() * geometries.length)];
+        const material = new THREE.MeshStandardMaterial({
+            color: Math.random() * 0xffffff,
+            roughness: 0.7,
+            metalness: 0.0,
+        });
+
+        const object = new THREE.Mesh(geometry, material);
+
+        object.position.x = Math.random() - 0.5;
+        object.position.y = Math.random() * 2 + 0.5;
+        object.position.z = Math.random() - 2.5;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.setScalar(Math.random() + 0.5);
+
+        object.castShadow = true;
+        object.receiveShadow = true;
+
+        group.add(object);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.setAnimationLoop(animate);
+    renderer.shadowMap.enabled = true;
+    renderer.xr.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    document.body.appendChild(XRButton.createButton(renderer));
+
+    // controllers
+
+    controller1 = renderer.xr.getController(0);
+    controller1.addEventListener('select', onSelect);
+    controller1.addEventListener('selectstart', onControllerEvent);
+    controller1.addEventListener('selectend', onControllerEvent);
+    controller1.addEventListener('move', onControllerEvent);
+    controller1.userData.active = false;
+    scene.add(controller1);
+
+    controller2 = renderer.xr.getController(1);
+    controller2.addEventListener('select', onSelect);
+    controller2.addEventListener('selectstart', onControllerEvent);
+    controller2.addEventListener('selectend', onControllerEvent);
+    controller2.addEventListener('move', onControllerEvent);
+    controller2.userData.active = true;
+    scene.add(controller2);
+
+    const controllerModelFactory = new XRControllerModelFactory();
+
+    controllerGrip1 = renderer.xr.getControllerGrip(0);
+    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
+    scene.add(controllerGrip1);
+
+    controllerGrip2 = renderer.xr.getControllerGrip(1);
+    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
+    scene.add(controllerGrip2);
+
+    //
+
+    const geometry = new THREE.BufferGeometry().setFromPoints([
+        new THREE.Vector3(0, 0, 0),
+        new THREE.Vector3(0, 0, -1),
+    ]);
+
+    line = new THREE.Line(geometry);
+    line.name = 'line';
+    line.scale.z = 5;
+
+    raycaster = new THREE.Raycaster();
+
+    // controls
+
+    controls = new TransformControls(camera, renderer.domElement);
+    controls.attach(group.children[0]);
+    scene.add(controls);
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onSelect(event) {
+    const controller = event.target;
+
+    controller1.userData.active = false;
+    controller2.userData.active = false;
+
+    if (controller === controller1) {
+        controller1.userData.active = true;
+        controller1.add(line);
+    }
+
+    if (controller === controller2) {
+        controller2.userData.active = true;
+        controller2.add(line);
+    }
+
+    raycaster.setFromXRController(controller);
+
+    const intersects = raycaster.intersectObjects(group.children);
+
+    if (intersects.length > 0) {
+        controls.attach(intersects[0].object);
+    }
+}
+
+function onControllerEvent(event) {
+    const controller = event.target;
+
+    if (controller.userData.active === false) return;
+
+    controls.getRaycaster().setFromXRController(controller);
+
+    switch (event.type) {
+        case 'selectstart':
+            controls.pointerDown(null);
+            break;
+
+        case 'selectend':
+            controls.pointerUp(null);
+            break;
+
+        case 'move':
+            controls.pointerHover(null);
+            controls.pointerMove(null);
+            break;
+    }
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+//
+
+function animate() {
+    renderer.render(scene, camera);
+}
diff --git a/examples-testing/examples/webxr_xr_dragging_custom_depth.ts b/examples-testing/examples/webxr_xr_dragging_custom_depth.ts
new file mode 100644
index 000000000..2cd50ba4c
--- /dev/null
+++ b/examples-testing/examples/webxr_xr_dragging_custom_depth.ts
@@ -0,0 +1,395 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { XRButton } from 'three/addons/webxr/XRButton.js';
+import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
+
+let container;
+let camera, scene, renderer;
+let controller1, controller2;
+let controllerGrip1, controllerGrip2;
+let isDepthSupplied = false;
+
+let raycaster;
+
+const intersected = [];
+const tempMatrix = new THREE.Matrix4();
+
+let controls, group;
+
+init();
+animate();
+
+function init() {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+
+    scene = new THREE.Scene();
+    scene.background = new THREE.Color(0x808080);
+
+    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
+    camera.position.set(0, 1.6, 3);
+
+    controls = new OrbitControls(camera, container);
+    controls.target.set(0, 1.6, 0);
+    controls.update();
+
+    const floorGeometry = new THREE.PlaneGeometry(6, 6);
+    const floorMaterial = new THREE.ShadowMaterial({
+        opacity: 0.25,
+        blending: THREE.CustomBlending,
+        transparent: false,
+    });
+    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
+    floor.rotation.x = -Math.PI / 2;
+    floor.receiveShadow = true;
+    scene.add(floor);
+
+    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
+
+    const light = new THREE.DirectionalLight(0xffffff, 3);
+    light.position.set(0, 6, 0);
+    light.castShadow = true;
+    light.shadow.camera.top = 3;
+    light.shadow.camera.bottom = -3;
+    light.shadow.camera.right = 3;
+    light.shadow.camera.left = -3;
+    light.shadow.mapSize.set(4096, 4096);
+    scene.add(light);
+
+    group = new THREE.Group();
+    scene.add(group);
+
+    const geometries = [
+        new THREE.BoxGeometry(0.2, 0.2, 0.2),
+        new THREE.ConeGeometry(0.2, 0.2, 64),
+        new THREE.CylinderGeometry(0.2, 0.2, 0.2, 64),
+        new THREE.IcosahedronGeometry(0.2, 8),
+        new THREE.TorusGeometry(0.2, 0.04, 64, 32),
+    ];
+
+    for (let i = 0; i < 50; i++) {
+        const geometry = geometries[Math.floor(Math.random() * geometries.length)];
+        const material = new THREE.ShaderMaterial({
+            vertexShader: /* glsl */ `
+							varying vec3 vNormal;
+							varying vec2 vUv;
+							void main() {
+								vNormal = normalize(normalMatrix * normal);
+								vUv = uv;
+								gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+							}
+						`,
+
+            fragmentShader: /* glsl */ `
+							uniform vec3 diffuseColor;
+							uniform float roughness;
+							uniform float metalness;
+							uniform float emissive;
+							varying vec3 vNormal;
+							varying vec2 vUv;
+							uniform sampler2DArray depthColor;
+							uniform float depthWidth;
+							uniform float depthHeight;
+							#define saturate( a ) clamp( a, 0.0, 1.0 )
+							float Depth_GetCameraDepthInMeters(const sampler2DArray depthTexture,
+								const vec2 depthUv, int arrayIndex) {
+								return texture(depthColor, vec3(depthUv.x, depthUv.y, arrayIndex)).r;
+							}
+							float Depth_GetOcclusion(const sampler2DArray depthTexture, const vec2 depthUv, float assetDepthM, int arrayIndex) {
+								float depthMm = Depth_GetCameraDepthInMeters(depthTexture, depthUv, arrayIndex);
+								const float kDepthTolerancePerM = 0.001;
+								return clamp(1.0 -
+									0.5 * (depthMm - assetDepthM) /
+										(kDepthTolerancePerM * assetDepthM) +
+									0.5, 0.0, 1.0);
+							}
+							float Depth_GetBlurredOcclusionAroundUV(const sampler2DArray depthTexture, const vec2 uv, float assetDepthM, int arrayIndex) {
+								// Kernel used:
+								// 0   4   7   4   0
+								// 4   16  26  16  4
+								// 7   26  41  26  7
+								// 4   16  26  16  4
+								// 0   4   7   4   0
+								const float kKernelTotalWeights = 269.0;
+								float sum = 0.0;
+								const float kOcclusionBlurAmount = 0.0005;
+								vec2 blurriness =
+								vec2(kOcclusionBlurAmount, kOcclusionBlurAmount /** u_DepthAspectRatio*/);
+								float current = 0.0;
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-1.0, -2.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+1.0, -2.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-1.0, +2.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+1.0, +2.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-2.0, +1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+2.0, +1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-2.0, -1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+2.0, -1.0) * blurriness, assetDepthM, arrayIndex);
+								sum += current * 4.0;
+								current = 0.0;
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-2.0, -0.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+2.0, +0.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+0.0, +2.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-0.0, -2.0) * blurriness, assetDepthM, arrayIndex);
+								sum += current * 7.0;
+								current = 0.0;
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-1.0, -1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+1.0, -1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-1.0, +1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+1.0, +1.0) * blurriness, assetDepthM, arrayIndex);
+								sum += current * 16.0;
+								current = 0.0;
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+0.0, +1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-0.0, -1.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(-1.0, -0.0) * blurriness, assetDepthM, arrayIndex);
+								current += Depth_GetOcclusion(
+								depthTexture, uv + vec2(+1.0, +0.0) * blurriness, assetDepthM, arrayIndex);
+								sum += current * 26.0;
+								sum += Depth_GetOcclusion(depthTexture, uv, assetDepthM, arrayIndex) * 41.0;
+								return sum / kKernelTotalWeights;
+							}
+							void main() {
+								vec3 normal = normalize(vNormal);
+								vec3 diffuse = diffuseColor;
+								float specularIntensity = pow(max(dot(normal, normalize(vec3(0, 0, 1))), 0.0), 64.0);
+								vec3 specular = vec3(specularIntensity) * mix(vec3(0.04), diffuse, metalness);
+								gl_FragColor = vec4(diffuse * (1.0 - specular) + specular, 1.0) * (1.0 + emissive);
+
+								if (depthWidth > 0.0) {
+									int arrayIndex = 0;
+									vec2 depthUv;
+									if (gl_FragCoord.x>=depthWidth) {
+										arrayIndex = 1;
+										depthUv = vec2((gl_FragCoord.x-depthWidth)/depthWidth, gl_FragCoord.y/depthHeight);
+									} else {
+										depthUv = vec2(gl_FragCoord.x/depthWidth, gl_FragCoord.y/depthHeight);
+									}
+									float assetDepthM = gl_FragCoord.z;
+
+									float occlusion = Depth_GetBlurredOcclusionAroundUV(depthColor, depthUv, assetDepthM, arrayIndex);
+									float depthMm = Depth_GetCameraDepthInMeters(depthColor, depthUv, arrayIndex);
+
+									float absDistance = abs(assetDepthM - depthMm);
+									float v = 0.0025;
+									absDistance = saturate(v - absDistance) / v;
+
+									gl_FragColor.rgb += vec3(absDistance * 2.0, absDistance * 2.0, absDistance * 12.0);
+									gl_FragColor = mix(gl_FragColor, vec4(0.0, 0.0, 0.0, 0.0), occlusion * 0.7);
+								}
+							}
+							`,
+
+            uniforms: {
+                diffuseColor: { value: new THREE.Color(Math.random() * 0xffffff) },
+                roughness: { value: 0.7 },
+                metalness: { value: 0.0 },
+                emissive: { value: 0.0 },
+                depthWidth: { value: 0.0 },
+                depthHeight: { value: 0.0 },
+                depthColor: { value: new THREE.Texture() },
+            },
+        });
+
+        const object = new THREE.Mesh(geometry, material);
+
+        object.position.x = Math.random() * 4 - 2;
+        object.position.y = Math.random() * 2;
+        object.position.z = Math.random() * 4 - 2;
+
+        object.rotation.x = Math.random() * 2 * Math.PI;
+        object.rotation.y = Math.random() * 2 * Math.PI;
+        object.rotation.z = Math.random() * 2 * Math.PI;
+
+        object.scale.setScalar(Math.random() + 0.5);
+
+        group.add(object);
+    }
+
+    //
+
+    renderer = new THREE.WebGLRenderer({ antialias: true });
+    renderer.setPixelRatio(window.devicePixelRatio);
+    renderer.setSize(window.innerWidth, window.innerHeight);
+    renderer.shadowMap.enabled = true;
+    renderer.xr.enabled = true;
+    container.appendChild(renderer.domElement);
+
+    document.body.appendChild(
+        XRButton.createButton(renderer, {
+            optionalFeatures: ['depth-sensing'],
+            depthSensing: { usagePreference: ['gpu-optimized'], dataFormatPreference: [] },
+        }),
+    );
+
+    // controllers
+
+    controller1 = renderer.xr.getController(0);
+    controller1.addEventListener('selectstart', onSelectStart);
+    controller1.addEventListener('selectend', onSelectEnd);
+    scene.add(controller1);
+
+    controller2 = renderer.xr.getController(1);
+    controller2.addEventListener('selectstart', onSelectStart);
+    controller2.addEventListener('selectend', onSelectEnd);
+    scene.add(controller2);
+
+    const controllerModelFactory = new XRControllerModelFactory();
+
+    controllerGrip1 = renderer.xr.getControllerGrip(0);
+    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
+    scene.add(controllerGrip1);
+
+    controllerGrip2 = renderer.xr.getControllerGrip(1);
+    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
+    scene.add(controllerGrip2);
+
+    //
+
+    const geometry = new THREE.BufferGeometry().setFromPoints([
+        new THREE.Vector3(0, 0, 0),
+        new THREE.Vector3(0, 0, -1),
+    ]);
+
+    const line = new THREE.Line(geometry);
+    line.name = 'line';
+    line.scale.z = 5;
+
+    controller1.add(line.clone());
+    controller2.add(line.clone());
+
+    raycaster = new THREE.Raycaster();
+
+    //
+
+    window.addEventListener('resize', onWindowResize);
+}
+
+function onWindowResize() {
+    camera.aspect = window.innerWidth / window.innerHeight;
+    camera.updateProjectionMatrix();
+
+    renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onSelectStart(event) {
+    const controller = event.target;
+
+    const intersections = getIntersections(controller);
+
+    if (intersections.length > 0) {
+        const intersection = intersections[0];
+
+        const object = intersection.object;
+        object.material.uniforms.emissive.value = 1;
+        controller.attach(object);
+
+        controller.userData.selected = object;
+    }
+
+    controller.userData.targetRayMode = event.data.targetRayMode;
+}
+
+function onSelectEnd(event) {
+    const controller = event.target;
+
+    if (controller.userData.selected !== undefined) {
+        const object = controller.userData.selected;
+        object.material.uniforms.emissive.value = 0;
+        group.attach(object);
+
+        controller.userData.selected = undefined;
+    }
+}
+
+function getIntersections(controller) {
+    controller.updateMatrixWorld();
+
+    tempMatrix.identity().extractRotation(controller.matrixWorld);
+
+    raycaster.ray.origin.setFromMatrixPosition(controller.matrixWorld);
+    raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);
+
+    return raycaster.intersectObjects(group.children, false);
+}
+
+function intersectObjects(controller) {
+    // Do not highlight in mobile-ar
+
+    if (controller.userData.targetRayMode === 'screen') return;
+
+    // Do not highlight when already selected
+
+    if (controller.userData.selected !== undefined) return;
+
+    const line = controller.getObjectByName('line');
+    const intersections = getIntersections(controller);
+
+    if (intersections.length > 0) {
+        const intersection = intersections[0];
+
+        const object = intersection.object;
+        object.material.uniforms.emissive.value = 1;
+        intersected.push(object);
+
+        line.scale.z = intersection.distance;
+    } else {
+        line.scale.z = 5;
+    }
+}
+
+function cleanIntersected() {
+    while (intersected.length) {
+        const object = intersected.pop();
+        object.material.uniforms.emissive.value = 0;
+    }
+}
+
+//
+
+function animate() {
+    renderer.setAnimationLoop(render);
+}
+
+function render() {
+    if (renderer.xr.hasDepthSensing() && !isDepthSupplied) {
+        group.children.forEach(child => {
+            child.material.uniforms.depthColor.value = renderer.xr.getDepthTexture();
+            child.material.uniforms.depthWidth.value = 1680;
+            child.material.uniforms.depthHeight.value = 1760;
+
+            isDepthSupplied = true;
+        });
+    } else if (!renderer.xr.hasDepthSensing() && isDepthSupplied) {
+        group.children.forEach(child => {
+            child.material.uniforms.depthWidth.value = 0;
+            child.material.uniforms.depthHeight.value = 0;
+
+            isDepthSupplied = false;
+        });
+    }
+
+    cleanIntersected();
+
+    intersectObjects(controller1);
+    intersectObjects(controller2);
+
+    renderer.render(scene, camera);
+}

From add9e5e3a52c6413657f66e722038f711d23b91e Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Thu, 29 Aug 2024 20:48:01 -0400
Subject: [PATCH 4/4] Update patch and delete examples

---
 examples-testing/examples/css2d_label.ts      | 186 ----
 examples-testing/examples/css3d_molecules.ts  | 353 --------
 .../examples/css3d_orthographic.ts            | 208 -----
 .../examples/css3d_periodictable.ts           | 793 ------------------
 examples-testing/examples/css3d_sandbox.ts    | 180 ----
 examples-testing/examples/css3d_sprites.ts    | 157 ----
 examples-testing/examples/css3d_youtube.ts    |  79 --
 examples-testing/examples/games_fps.ts        | 372 --------
 .../examples/misc_animation_groups.ts         | 125 ---
 .../examples/misc_animation_keys.ts           | 129 ---
 .../examples/misc_boxselection.ts             | 137 ---
 .../examples/misc_controls_arcball.ts         | 210 -----
 .../examples/misc_controls_drag.ts            | 153 ----
 .../examples/misc_controls_fly.ts             | 214 -----
 .../examples/misc_controls_map.ts             |  98 ---
 .../examples/misc_controls_orbit.ts           |  89 --
 .../examples/misc_controls_pointerlock.ts     | 245 ------
 .../examples/misc_controls_trackball.ts       | 134 ---
 .../examples/misc_controls_transform.ts       | 181 ----
 .../examples/misc_exporter_draco.ts           | 117 ---
 .../examples/misc_exporter_exr.ts             | 158 ----
 .../examples/misc_exporter_gltf.ts            | 507 -----------
 .../examples/misc_exporter_obj.ts             | 192 -----
 .../examples/misc_exporter_ply.ts             | 156 ----
 .../examples/misc_exporter_stl.ts             | 129 ---
 .../examples/misc_exporter_usdz.ts            | 129 ---
 examples-testing/examples/misc_lookat.ts      |  95 ---
 examples-testing/examples/misc_uv_tests.ts    |  44 -
 .../examples/physics_ammo_instancing.ts       | 119 ---
 .../examples/physics_jolt_instancing.ts       | 119 ---
 .../examples/physics_rapier_instancing.ts     | 119 ---
 examples-testing/examples/svg_lines.ts        |  87 --
 examples-testing/examples/svg_sandbox.ts      | 212 -----
 .../examples/webaudio_orientation.ts          | 141 ----
 examples-testing/examples/webaudio_sandbox.ts | 222 -----
 examples-testing/examples/webaudio_timing.ts  | 152 ----
 .../examples/webaudio_visualizer.ts           |  86 --
 .../examples/webgl_animation_keyframes.ts     |  80 --
 .../examples/webgl_animation_multiple.ts      | 197 -----
 .../webgl_animation_skinning_morph.ts         | 187 -----
 .../examples/webgl_buffergeometry.ts          | 178 ----
 ...webgl_buffergeometry_attributes_integer.ts | 113 ---
 .../webgl_buffergeometry_attributes_none.ts   |  56 --
 ...fergeometry_custom_attributes_particles.ts | 103 ---
 .../webgl_buffergeometry_drawrange.ts         | 239 ------
 .../webgl_buffergeometry_glbufferattribute.ts | 139 ---
 .../examples/webgl_buffergeometry_indexed.ts  | 137 ---
 .../webgl_buffergeometry_instancing.ts        | 138 ---
 ...gl_buffergeometry_instancing_billboards.ts |  86 --
 ...l_buffergeometry_instancing_interleaved.ts | 152 ----
 .../examples/webgl_buffergeometry_lines.ts    | 118 ---
 .../webgl_buffergeometry_lines_indexed.ts     | 179 ----
 .../examples/webgl_buffergeometry_points.ts   | 109 ---
 ...webgl_buffergeometry_points_interleaved.ts | 122 ---
 .../webgl_buffergeometry_rawshader.ts         |  97 ---
 .../webgl_buffergeometry_selective_draw.ts    | 150 ----
 .../examples/webgl_buffergeometry_uint.ts     | 177 ----
 examples-testing/examples/webgl_camera.ts     | 218 -----
 .../examples/webgl_camera_array.ts            | 104 ---
 .../webgl_camera_logarithmicdepthbuffer.ts    | 248 ------
 .../examples/webgl_clipculldistance.ts        | 110 ---
 examples-testing/examples/webgl_clipping.ts   | 195 -----
 .../examples/webgl_clipping_advanced.ts       | 355 --------
 .../examples/webgl_clipping_intersection.ts   | 137 ---
 .../examples/webgl_clipping_stencil.ts        | 260 ------
 .../examples/webgl_custom_attributes.ts       | 100 ---
 .../examples/webgl_custom_attributes_lines.ts | 121 ---
 .../webgl_custom_attributes_points.ts         | 117 ---
 .../webgl_custom_attributes_points2.ts        | 193 -----
 .../webgl_custom_attributes_points3.ts        | 200 -----
 examples-testing/examples/webgl_decals.ts     | 237 ------
 .../examples/webgl_effects_anaglyph.ts        | 114 ---
 .../examples/webgl_effects_ascii.ts           |  81 --
 .../examples/webgl_effects_parallaxbarrier.ts | 110 ---
 .../examples/webgl_effects_peppersghost.ts    |  85 --
 .../examples/webgl_effects_stereo.ts          |  98 ---
 .../examples/webgl_framebuffer_texture.ts     | 151 ----
 .../examples/webgl_furnace_test.ts            |  96 ---
 examples-testing/examples/webgl_geometries.ts | 137 ---
 .../examples/webgl_geometries_parametric.ts   | 124 ---
 .../examples/webgl_geometry_colors.ts         | 176 ----
 .../webgl_geometry_colors_lookuptable.ts      | 148 ----
 .../examples/webgl_geometry_convex.ts         | 117 ---
 .../examples/webgl_geometry_cube.ts           |  46 -
 .../examples/webgl_geometry_dynamic.ts        |  97 ---
 .../examples/webgl_geometry_extrude_shapes.ts | 149 ----
 .../webgl_geometry_extrude_splines.ts         | 310 -------
 .../examples/webgl_geometry_minecraft.ts      | 183 ----
 .../examples/webgl_geometry_nurbs.ts          | 298 -------
 .../examples/webgl_geometry_sdf.ts            | 164 ----
 .../examples/webgl_geometry_shapes.ts         | 363 --------
 .../examples/webgl_geometry_teapot.ts         | 180 ----
 .../examples/webgl_geometry_terrain.ts        | 173 ----
 .../webgl_geometry_terrain_raycast.ts         | 206 -----
 .../examples/webgl_geometry_text.ts           | 312 -------
 .../examples/webgl_geometry_text_shapes.ts    | 112 ---
 .../examples/webgl_geometry_text_stroke.ts    | 116 ---
 .../examples/webgl_gpgpu_birds.ts             | 313 -------
 .../examples/webgl_gpgpu_birds_gltf.ts        | 415 ---------
 .../examples/webgl_gpgpu_protoplanet.ts       | 280 -------
 .../examples/webgl_gpgpu_water.ts             | 397 ---------
 examples-testing/examples/webgl_helpers.ts    | 117 ---
 .../examples/webgl_instancing_dynamic.ts      | 103 ---
 .../examples/webgl_instancing_morph.ts        | 147 ----
 .../examples/webgl_instancing_performance.ts  | 262 ------
 .../examples/webgl_instancing_raycast.ts      | 116 ---
 .../examples/webgl_instancing_scatter.ts      | 257 ------
 .../webgl_interactive_buffergeometry.ts       | 244 ------
 .../examples/webgl_interactive_cubes.ts       | 114 ---
 .../examples/webgl_interactive_cubes_gpu.ts   | 229 -----
 .../examples/webgl_interactive_cubes_ortho.ts | 129 ---
 .../examples/webgl_interactive_lines.ts       | 160 ----
 .../examples/webgl_interactive_points.ts      | 143 ----
 .../webgl_interactive_raycasting_points.ts    | 220 -----
 .../webgl_interactive_voxelpainter.ts         | 158 ----
 examples-testing/examples/webgl_layers.ts     | 125 ---
 examples-testing/examples/webgl_lensflares.ts | 137 ---
 examples-testing/examples/webgl_lightprobe.ts | 133 ---
 .../examples/webgl_lightprobe_cubecamera.ts   |  83 --
 .../examples/webgl_lights_hemisphere.ts       | 188 -----
 .../examples/webgl_lights_physical.ts         | 237 ------
 .../examples/webgl_lights_pointlights.ts      | 100 ---
 .../examples/webgl_lights_rectarealight.ts    |  79 --
 .../examples/webgl_lights_spotlight.ts        | 183 ----
 .../examples/webgl_lights_spotlights.ts       | 133 ---
 .../examples/webgl_lines_colors.ts            | 181 ----
 .../examples/webgl_lines_dashed.ts            | 186 ----
 examples-testing/examples/webgl_lines_fat.ts  | 251 ------
 .../examples/webgl_lines_fat_raycasting.ts    | 294 -------
 .../examples/webgl_lines_fat_wireframe.ts     | 210 -----
 examples-testing/examples/webgl_loader_3dm.ts |  95 ---
 examples-testing/examples/webgl_loader_3ds.ts |  62 --
 examples-testing/examples/webgl_loader_3mf.ts | 105 ---
 .../examples/webgl_loader_3mf_materials.ts    | 106 ---
 examples-testing/examples/webgl_loader_amf.ts |  62 --
 examples-testing/examples/webgl_loader_bvh.ts |  61 --
 .../examples/webgl_loader_collada.ts          |  83 --
 .../examples/webgl_loader_collada_skinning.ts |  97 ---
 .../examples/webgl_loader_draco.ts            |  85 --
 examples-testing/examples/webgl_loader_fbx.ts | 162 ----
 .../examples/webgl_loader_fbx_nurbs.ts        |  61 --
 .../examples/webgl_loader_gcode.ts            |  49 --
 .../examples/webgl_loader_gltf.ts             |  74 --
 .../examples/webgl_loader_gltf_anisotropy.ts  |  68 --
 .../examples/webgl_loader_gltf_avif.ts        |  61 --
 .../examples/webgl_loader_gltf_compressed.ts  |  83 --
 .../examples/webgl_loader_gltf_dispersion.ts  |  66 --
 .../examples/webgl_loader_gltf_instancing.ts  |  69 --
 .../examples/webgl_loader_gltf_iridescence.ts |  66 --
 .../examples/webgl_loader_gltf_sheen.ts       |  72 --
 .../webgl_loader_gltf_transmission.ts         |  80 --
 .../examples/webgl_loader_imagebitmap.ts      | 109 ---
 examples-testing/examples/webgl_loader_kmz.ts |  59 --
 examples-testing/examples/webgl_loader_lwo.ts |  69 --
 .../examples/webgl_loader_md2_control.ts      | 289 -------
 examples-testing/examples/webgl_loader_mdd.ts |  62 --
 examples-testing/examples/webgl_loader_obj.ts |  98 ---
 .../examples/webgl_loader_obj_mtl.ts          |  82 --
 examples-testing/examples/webgl_loader_pcd.ts |  65 --
 examples-testing/examples/webgl_loader_pdb.ts | 208 -----
 examples-testing/examples/webgl_loader_ply.ts | 146 ----
 examples-testing/examples/webgl_loader_svg.ts | 193 -----
 .../examples/webgl_loader_texture_dds.ts      | 207 -----
 .../examples/webgl_loader_texture_ktx.ts      | 137 ---
 .../examples/webgl_loader_texture_rgbm.ts     |  75 --
 .../examples/webgl_loader_texture_tga.ts      |  90 --
 .../examples/webgl_loader_texture_tiff.ts     |  87 --
 .../examples/webgl_loader_texture_ultrahdr.ts | 101 ---
 .../examples/webgl_loader_tilt.ts             |  54 --
 examples-testing/examples/webgl_loader_ttf.ts | 231 -----
 .../examples/webgl_loader_usdz.ts             |  68 --
 examples-testing/examples/webgl_loader_vox.ts | 104 ---
 .../examples/webgl_loader_vrml.ts             | 118 ---
 examples-testing/examples/webgl_loader_vtk.ts | 123 ---
 examples-testing/examples/webgl_loader_xyz.ts |  62 --
 examples-testing/examples/webgl_lod.ts        |  88 --
 .../examples/webgl_marchingcubes.ts           | 311 -------
 .../examples/webgl_materials_alphahash.ts     | 178 ----
 .../examples/webgl_materials_blending.ts      | 147 ----
 .../webgl_materials_blending_custom.ts        | 214 -----
 .../examples/webgl_materials_bumpmap.ts       | 140 ----
 .../examples/webgl_materials_car.ts           | 167 ----
 .../examples/webgl_materials_cubemap.ts       | 115 ---
 .../webgl_materials_cubemap_dynamic.ts        | 115 ---
 .../webgl_materials_cubemap_mipmaps.ts        | 119 ---
 .../webgl_materials_cubemap_refraction.ts     | 126 ---
 ...bgl_materials_cubemap_render_to_mipmaps.ts | 183 ----
 .../webgl_materials_displacementmap.ts        | 224 -----
 .../examples/webgl_materials_envmaps.ts       | 131 ---
 .../examples/webgl_materials_envmaps_exr.ts   | 153 ----
 ...webgl_materials_envmaps_groundprojected.ts | 150 ----
 .../examples/webgl_materials_envmaps_hdr.ts   | 176 ----
 .../examples/webgl_materials_modified.ts      | 115 ---
 .../webgl_materials_normalmap_object_space.ts |  82 --
 .../webgl_materials_physical_clearcoat.ts     | 208 -----
 .../webgl_materials_physical_transmission.ts  | 182 ----
 ...l_materials_physical_transmission_alpha.ts | 192 -----
 .../webgl_materials_texture_anisotropy.ts     | 143 ----
 .../webgl_materials_texture_canvas.ts         |  92 --
 .../webgl_materials_texture_filters.ts        | 164 ----
 .../webgl_materials_texture_manualmipmap.ts   | 175 ----
 .../webgl_materials_texture_partialupdate.ts  | 100 ---
 .../webgl_materials_texture_rotation.ts       | 113 ---
 .../examples/webgl_materials_toon.ts          | 152 ----
 .../examples/webgl_materials_video.ts         | 208 -----
 .../examples/webgl_materials_video_webcam.ts  |  79 --
 .../examples/webgl_materials_wireframe.ts     | 107 ---
 examples-testing/examples/webgl_math_obb.ts   | 189 -----
 .../webgl_math_orientation_transform.ts       |  95 ---
 examples-testing/examples/webgl_mesh_batch.ts | 305 -------
 examples-testing/examples/webgl_mirror.ts     | 168 ----
 .../examples/webgl_modifier_edgesplit.ts      | 136 ---
 .../examples/webgl_modifier_simplifier.ts     |  77 --
 .../examples/webgl_modifier_tessellation.ts   | 142 ----
 .../examples/webgl_morphtargets.ts            | 120 ---
 .../examples/webgl_morphtargets_face.ts       | 105 ---
 .../examples/webgl_morphtargets_horse.ts      | 100 ---
 .../examples/webgl_morphtargets_sphere.ts     | 105 ---
 .../examples/webgl_multiple_elements.ts       | 139 ---
 .../examples/webgl_multiple_rendertargets.ts  | 133 ---
 .../webgl_multiple_scenes_comparison.ts       |  98 ---
 .../examples/webgl_multiple_views.ts          | 237 ------
 .../webgl_multisampled_renderbuffers.ts       | 133 ---
 .../examples/webgl_panorama_cube.ts           |  83 --
 .../webgl_panorama_equirectangular.ts         | 112 ---
 .../examples/webgl_performance.ts             |  77 --
 examples-testing/examples/webgl_pmrem_test.ts | 141 ----
 .../examples/webgl_points_billboards.ts       | 120 ---
 .../examples/webgl_points_sprites.ts          | 167 ----
 .../examples/webgl_points_waves.ts            | 145 ----
 examples-testing/examples/webgl_portal.ts     | 218 -----
 .../examples/webgl_postprocessing.ts          |  86 --
 .../examples/webgl_postprocessing_advanced.ts | 304 -------
 .../webgl_postprocessing_afterimage.ts        |  72 --
 .../webgl_postprocessing_backgrounds.ts       | 214 -----
 .../examples/webgl_postprocessing_fxaa.ts     | 132 ---
 .../examples/webgl_postprocessing_glitch.ts   |  97 ---
 .../examples/webgl_postprocessing_godrays.ts  | 347 --------
 .../examples/webgl_postprocessing_gtao.ts     | 215 -----
 .../examples/webgl_postprocessing_masking.ts  | 100 ---
 .../webgl_postprocessing_material_ao.ts       | 277 ------
 .../examples/webgl_postprocessing_outline.ts  | 282 -------
 .../examples/webgl_postprocessing_pixel.ts    | 228 -----
 .../webgl_postprocessing_procedural.ts        |  77 --
 .../webgl_postprocessing_rgb_halftone.ts      | 167 ----
 .../examples/webgl_postprocessing_sao.ts      | 137 ---
 .../examples/webgl_postprocessing_smaa.ts     | 109 ---
 .../examples/webgl_postprocessing_sobel.ts    | 111 ---
 .../examples/webgl_postprocessing_ssaa.ts     | 206 -----
 .../examples/webgl_postprocessing_ssao.ts     | 118 ---
 .../examples/webgl_postprocessing_ssr.ts      | 261 ------
 .../examples/webgl_postprocessing_taa.ts      | 139 ---
 .../webgl_postprocessing_transition.ts        | 211 -----
 .../webgl_postprocessing_unreal_bloom.ts      | 136 ---
 ...l_postprocessing_unreal_bloom_selective.ts | 195 -----
 .../examples/webgl_raycaster_sprite.ts        | 103 ---
 .../examples/webgl_raycaster_texture.ts       | 286 -------
 .../examples/webgl_raymarching_reflect.ts     |  95 ---
 .../examples/webgl_read_float_buffer.ts       | 153 ----
 examples-testing/examples/webgl_refraction.ts | 135 ---
 examples-testing/examples/webgl_rtt.ts        | 171 ----
 examples-testing/examples/webgl_shader.ts     |  50 --
 .../examples/webgl_shader_lava.ts             | 101 ---
 .../examples/webgl_shaders_ocean.ts           | 169 ----
 .../examples/webgl_shaders_sky.ts             | 103 ---
 .../examples/webgl_shadow_contact.ts          | 272 ------
 examples-testing/examples/webgl_shadowmap.ts  | 311 -------
 .../examples/webgl_shadowmap_csm.ts           | 253 ------
 .../examples/webgl_shadowmap_pcss.ts          | 161 ----
 .../examples/webgl_shadowmap_performance.ts   | 281 -------
 .../examples/webgl_shadowmap_pointlight.ts    | 139 ---
 .../examples/webgl_shadowmap_progressive.ts   | 204 -----
 .../examples/webgl_shadowmap_viewer.ts        | 178 ----
 .../examples/webgl_shadowmap_vsm.ts           | 200 -----
 examples-testing/examples/webgl_shadowmesh.ts | 250 ------
 examples-testing/examples/webgl_simple_gi.ts  | 172 ----
 examples-testing/examples/webgl_sprites.ts    | 187 -----
 .../examples/webgl_test_memory.ts             |  65 --
 .../examples/webgl_test_memory2.ts            |  81 --
 .../examples/webgl_test_wide_gamut.ts         | 118 ---
 .../webgl_texture2darray_compressed.ts        |  88 --
 .../webgl_texture2darray_layerupdate.ts       | 131 ---
 examples-testing/examples/webgl_texture3d.ts  | 128 ---
 .../examples/webgl_texture3d_partialupdate.ts | 326 -------
 .../examples/webgl_tonemapping.ts             | 163 ----
 examples-testing/examples/webgl_ubo.ts        | 137 ---
 examples-testing/examples/webgl_ubo_arrays.ts | 171 ----
 .../examples/webgl_video_kinect.ts            | 113 ---
 .../webgl_video_panorama_equirectangular.ts   |  95 ---
 .../examples/webgl_volume_cloud.ts            | 279 ------
 .../examples/webgl_volume_instancing.ts       | 192 -----
 .../examples/webgl_volume_perlin.ts           | 208 -----
 examples-testing/examples/webgl_water.ts      | 162 ----
 .../examples/webgl_water_flowmap.ts           | 100 ---
 .../examples/webgpu_backdrop_area.ts          | 164 ----
 .../webgpu_camera_logarithmicdepthbuffer.ts   | 245 ------
 examples-testing/examples/webgpu_clearcoat.ts | 205 -----
 examples-testing/examples/webgpu_clipping.ts  | 207 -----
 .../examples/webgpu_custom_fog_background.ts  |  93 --
 .../examples/webgpu_display_stereo.ts         | 139 ---
 .../examples/webgpu_instancing_morph.ts       | 148 ----
 .../examples/webgpu_lightprobe.ts             | 126 ---
 .../examples/webgpu_lights_ies_spotlight.ts   | 117 ---
 .../examples/webgpu_lights_rectarealight.ts   |  79 --
 .../examples/webgpu_loader_gltf.ts            |  71 --
 .../examples/webgpu_loader_gltf_anisotropy.ts |  65 --
 .../examples/webgpu_loader_gltf_compressed.ts |  67 --
 .../examples/webgpu_loader_gltf_dispersion.ts |  63 --
 .../webgpu_loader_gltf_iridescence.ts         |  70 --
 .../examples/webgpu_loader_gltf_sheen.ts      |  81 --
 .../webgpu_loader_gltf_transmission.ts        |  80 --
 .../examples/webgpu_materials_basic.ts        | 137 ---
 .../webgpu_materials_displacementmap.ts       | 224 -----
 .../examples/webgpu_materials_envmaps.ts      | 107 ---
 .../examples/webgpu_materials_lightmap.ts     |  94 ---
 .../examples/webgpu_materials_toon.ts         | 148 ----
 .../examples/webgpu_materials_transmission.ts | 168 ----
 .../examples/webgpu_materials_video.ts        | 184 ----
 .../examples/webgpu_mesh_batch.ts             | 269 ------
 .../examples/webgpu_morphtargets.ts           | 121 ---
 .../examples/webgpu_morphtargets_face.ts      | 102 ---
 examples-testing/examples/webgpu_mrt.ts       | 120 ---
 .../webgpu_multiple_rendertargets_readback.ts | 156 ----
 examples-testing/examples/webgpu_ocean.ts     | 161 ----
 .../examples/webgpu_parallax_uv.ts            | 112 ---
 .../examples/webgpu_performance.ts            |  84 --
 .../examples/webgpu_postprocessing.ts         |  77 --
 .../examples/webgpu_postprocessing_3dlut.ts   | 139 ---
 .../webgpu_postprocessing_afterimage.ts       |  70 --
 .../examples/webgpu_postprocessing_ao.ts      | 204 -----
 .../examples/webgpu_postprocessing_bloom.ts   | 127 ---
 .../webgpu_postprocessing_bloom_emissive.ts   | 100 ---
 .../webgpu_postprocessing_bloom_selective.ts  | 122 ---
 .../webgpu_postprocessing_difference.ts       |  92 --
 .../examples/webgpu_postprocessing_dof.ts     | 160 ----
 .../examples/webgpu_postprocessing_fxaa.ts    | 122 ---
 .../examples/webgpu_postprocessing_masking.ts |  85 --
 .../webgpu_postprocessing_motion_blur.ts      | 205 -----
 .../examples/webgpu_postprocessing_pixel.ts   | 234 ------
 .../examples/webgpu_postprocessing_sobel.ts   |  89 --
 .../examples/webgpu_postprocessing_ssaa.ts    | 181 ----
 .../webgpu_postprocessing_transition.ts       | 200 -----
 .../examples/webgpu_procedural_texture.ts     |  74 --
 .../examples/webgpu_refraction.ts             | 141 ----
 examples-testing/examples/webgpu_sky.ts       |  95 ---
 .../webgpu_texture2darray_compressed.ts       |  84 --
 .../examples/webgpu_textures_anisotropy.ts    | 155 ----
 .../examples/webgpu_textures_partialupdate.ts | 103 ---
 .../examples/webgpu_tsl_coffee_smoke.ts       | 145 ----
 .../examples/webgpu_tsl_vfx_flames.ts         | 203 -----
 .../examples/webgpu_video_panorama.ts         |  99 ---
 examples-testing/examples/webgpu_water.ts     | 171 ----
 examples-testing/examples/webxr_ar_cones.ts   |  66 --
 examples-testing/examples/webxr_ar_hittest.ts | 115 ---
 .../examples/webxr_ar_lighting.ts             | 124 ---
 .../examples/webxr_ar_plane_detection.ts      |  46 -
 .../examples/webxr_vr_handinput.ts            | 126 ---
 .../examples/webxr_vr_panorama.ts             |  92 --
 .../examples/webxr_vr_panorama_depth.ts       |  90 --
 .../examples/webxr_vr_rollercoaster.ts        | 211 -----
 examples-testing/examples/webxr_vr_sandbox.ts | 192 -----
 examples-testing/examples/webxr_vr_video.ts   |  92 --
 .../examples/webxr_xr_controls_transform.ts   | 210 -----
 .../webxr_xr_dragging_custom_depth.ts         | 395 ---------
 364 files changed, 55542 deletions(-)
 delete mode 100644 examples-testing/examples/css2d_label.ts
 delete mode 100644 examples-testing/examples/css3d_molecules.ts
 delete mode 100644 examples-testing/examples/css3d_orthographic.ts
 delete mode 100644 examples-testing/examples/css3d_periodictable.ts
 delete mode 100644 examples-testing/examples/css3d_sandbox.ts
 delete mode 100644 examples-testing/examples/css3d_sprites.ts
 delete mode 100644 examples-testing/examples/css3d_youtube.ts
 delete mode 100644 examples-testing/examples/games_fps.ts
 delete mode 100644 examples-testing/examples/misc_animation_groups.ts
 delete mode 100644 examples-testing/examples/misc_animation_keys.ts
 delete mode 100644 examples-testing/examples/misc_boxselection.ts
 delete mode 100644 examples-testing/examples/misc_controls_arcball.ts
 delete mode 100644 examples-testing/examples/misc_controls_drag.ts
 delete mode 100644 examples-testing/examples/misc_controls_fly.ts
 delete mode 100644 examples-testing/examples/misc_controls_map.ts
 delete mode 100644 examples-testing/examples/misc_controls_orbit.ts
 delete mode 100644 examples-testing/examples/misc_controls_pointerlock.ts
 delete mode 100644 examples-testing/examples/misc_controls_trackball.ts
 delete mode 100644 examples-testing/examples/misc_controls_transform.ts
 delete mode 100644 examples-testing/examples/misc_exporter_draco.ts
 delete mode 100644 examples-testing/examples/misc_exporter_exr.ts
 delete mode 100644 examples-testing/examples/misc_exporter_gltf.ts
 delete mode 100644 examples-testing/examples/misc_exporter_obj.ts
 delete mode 100644 examples-testing/examples/misc_exporter_ply.ts
 delete mode 100644 examples-testing/examples/misc_exporter_stl.ts
 delete mode 100644 examples-testing/examples/misc_exporter_usdz.ts
 delete mode 100644 examples-testing/examples/misc_lookat.ts
 delete mode 100644 examples-testing/examples/misc_uv_tests.ts
 delete mode 100644 examples-testing/examples/physics_ammo_instancing.ts
 delete mode 100644 examples-testing/examples/physics_jolt_instancing.ts
 delete mode 100644 examples-testing/examples/physics_rapier_instancing.ts
 delete mode 100644 examples-testing/examples/svg_lines.ts
 delete mode 100644 examples-testing/examples/svg_sandbox.ts
 delete mode 100644 examples-testing/examples/webaudio_orientation.ts
 delete mode 100644 examples-testing/examples/webaudio_sandbox.ts
 delete mode 100644 examples-testing/examples/webaudio_timing.ts
 delete mode 100644 examples-testing/examples/webaudio_visualizer.ts
 delete mode 100644 examples-testing/examples/webgl_animation_keyframes.ts
 delete mode 100644 examples-testing/examples/webgl_animation_multiple.ts
 delete mode 100644 examples-testing/examples/webgl_animation_skinning_morph.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_attributes_none.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_drawrange.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_indexed.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_instancing.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_lines.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_points.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_rawshader.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_selective_draw.ts
 delete mode 100644 examples-testing/examples/webgl_buffergeometry_uint.ts
 delete mode 100644 examples-testing/examples/webgl_camera.ts
 delete mode 100644 examples-testing/examples/webgl_camera_array.ts
 delete mode 100644 examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
 delete mode 100644 examples-testing/examples/webgl_clipculldistance.ts
 delete mode 100644 examples-testing/examples/webgl_clipping.ts
 delete mode 100644 examples-testing/examples/webgl_clipping_advanced.ts
 delete mode 100644 examples-testing/examples/webgl_clipping_intersection.ts
 delete mode 100644 examples-testing/examples/webgl_clipping_stencil.ts
 delete mode 100644 examples-testing/examples/webgl_custom_attributes.ts
 delete mode 100644 examples-testing/examples/webgl_custom_attributes_lines.ts
 delete mode 100644 examples-testing/examples/webgl_custom_attributes_points.ts
 delete mode 100644 examples-testing/examples/webgl_custom_attributes_points2.ts
 delete mode 100644 examples-testing/examples/webgl_custom_attributes_points3.ts
 delete mode 100644 examples-testing/examples/webgl_decals.ts
 delete mode 100644 examples-testing/examples/webgl_effects_anaglyph.ts
 delete mode 100644 examples-testing/examples/webgl_effects_ascii.ts
 delete mode 100644 examples-testing/examples/webgl_effects_parallaxbarrier.ts
 delete mode 100644 examples-testing/examples/webgl_effects_peppersghost.ts
 delete mode 100644 examples-testing/examples/webgl_effects_stereo.ts
 delete mode 100644 examples-testing/examples/webgl_framebuffer_texture.ts
 delete mode 100644 examples-testing/examples/webgl_furnace_test.ts
 delete mode 100644 examples-testing/examples/webgl_geometries.ts
 delete mode 100644 examples-testing/examples/webgl_geometries_parametric.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_colors.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_colors_lookuptable.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_convex.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_cube.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_dynamic.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_extrude_shapes.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_extrude_splines.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_minecraft.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_nurbs.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_sdf.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_shapes.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_teapot.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_terrain.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_terrain_raycast.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_text.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_text_shapes.ts
 delete mode 100644 examples-testing/examples/webgl_geometry_text_stroke.ts
 delete mode 100644 examples-testing/examples/webgl_gpgpu_birds.ts
 delete mode 100644 examples-testing/examples/webgl_gpgpu_birds_gltf.ts
 delete mode 100644 examples-testing/examples/webgl_gpgpu_protoplanet.ts
 delete mode 100644 examples-testing/examples/webgl_gpgpu_water.ts
 delete mode 100644 examples-testing/examples/webgl_helpers.ts
 delete mode 100644 examples-testing/examples/webgl_instancing_dynamic.ts
 delete mode 100644 examples-testing/examples/webgl_instancing_morph.ts
 delete mode 100644 examples-testing/examples/webgl_instancing_performance.ts
 delete mode 100644 examples-testing/examples/webgl_instancing_raycast.ts
 delete mode 100644 examples-testing/examples/webgl_instancing_scatter.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_buffergeometry.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_cubes.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_cubes_gpu.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_cubes_ortho.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_lines.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_points.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_raycasting_points.ts
 delete mode 100644 examples-testing/examples/webgl_interactive_voxelpainter.ts
 delete mode 100644 examples-testing/examples/webgl_layers.ts
 delete mode 100644 examples-testing/examples/webgl_lensflares.ts
 delete mode 100644 examples-testing/examples/webgl_lightprobe.ts
 delete mode 100644 examples-testing/examples/webgl_lightprobe_cubecamera.ts
 delete mode 100644 examples-testing/examples/webgl_lights_hemisphere.ts
 delete mode 100644 examples-testing/examples/webgl_lights_physical.ts
 delete mode 100644 examples-testing/examples/webgl_lights_pointlights.ts
 delete mode 100644 examples-testing/examples/webgl_lights_rectarealight.ts
 delete mode 100644 examples-testing/examples/webgl_lights_spotlight.ts
 delete mode 100644 examples-testing/examples/webgl_lights_spotlights.ts
 delete mode 100644 examples-testing/examples/webgl_lines_colors.ts
 delete mode 100644 examples-testing/examples/webgl_lines_dashed.ts
 delete mode 100644 examples-testing/examples/webgl_lines_fat.ts
 delete mode 100644 examples-testing/examples/webgl_lines_fat_raycasting.ts
 delete mode 100644 examples-testing/examples/webgl_lines_fat_wireframe.ts
 delete mode 100644 examples-testing/examples/webgl_loader_3dm.ts
 delete mode 100644 examples-testing/examples/webgl_loader_3ds.ts
 delete mode 100644 examples-testing/examples/webgl_loader_3mf.ts
 delete mode 100644 examples-testing/examples/webgl_loader_3mf_materials.ts
 delete mode 100644 examples-testing/examples/webgl_loader_amf.ts
 delete mode 100644 examples-testing/examples/webgl_loader_bvh.ts
 delete mode 100644 examples-testing/examples/webgl_loader_collada.ts
 delete mode 100644 examples-testing/examples/webgl_loader_collada_skinning.ts
 delete mode 100644 examples-testing/examples/webgl_loader_draco.ts
 delete mode 100644 examples-testing/examples/webgl_loader_fbx.ts
 delete mode 100644 examples-testing/examples/webgl_loader_fbx_nurbs.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gcode.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_anisotropy.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_avif.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_compressed.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_dispersion.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_instancing.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_iridescence.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_sheen.ts
 delete mode 100644 examples-testing/examples/webgl_loader_gltf_transmission.ts
 delete mode 100644 examples-testing/examples/webgl_loader_imagebitmap.ts
 delete mode 100644 examples-testing/examples/webgl_loader_kmz.ts
 delete mode 100644 examples-testing/examples/webgl_loader_lwo.ts
 delete mode 100644 examples-testing/examples/webgl_loader_md2_control.ts
 delete mode 100644 examples-testing/examples/webgl_loader_mdd.ts
 delete mode 100644 examples-testing/examples/webgl_loader_obj.ts
 delete mode 100644 examples-testing/examples/webgl_loader_obj_mtl.ts
 delete mode 100644 examples-testing/examples/webgl_loader_pcd.ts
 delete mode 100644 examples-testing/examples/webgl_loader_pdb.ts
 delete mode 100644 examples-testing/examples/webgl_loader_ply.ts
 delete mode 100644 examples-testing/examples/webgl_loader_svg.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_dds.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_ktx.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_rgbm.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_tga.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_tiff.ts
 delete mode 100644 examples-testing/examples/webgl_loader_texture_ultrahdr.ts
 delete mode 100644 examples-testing/examples/webgl_loader_tilt.ts
 delete mode 100644 examples-testing/examples/webgl_loader_ttf.ts
 delete mode 100644 examples-testing/examples/webgl_loader_usdz.ts
 delete mode 100644 examples-testing/examples/webgl_loader_vox.ts
 delete mode 100644 examples-testing/examples/webgl_loader_vrml.ts
 delete mode 100644 examples-testing/examples/webgl_loader_vtk.ts
 delete mode 100644 examples-testing/examples/webgl_loader_xyz.ts
 delete mode 100644 examples-testing/examples/webgl_lod.ts
 delete mode 100644 examples-testing/examples/webgl_marchingcubes.ts
 delete mode 100644 examples-testing/examples/webgl_materials_alphahash.ts
 delete mode 100644 examples-testing/examples/webgl_materials_blending.ts
 delete mode 100644 examples-testing/examples/webgl_materials_blending_custom.ts
 delete mode 100644 examples-testing/examples/webgl_materials_bumpmap.ts
 delete mode 100644 examples-testing/examples/webgl_materials_car.ts
 delete mode 100644 examples-testing/examples/webgl_materials_cubemap.ts
 delete mode 100644 examples-testing/examples/webgl_materials_cubemap_dynamic.ts
 delete mode 100644 examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
 delete mode 100644 examples-testing/examples/webgl_materials_cubemap_refraction.ts
 delete mode 100644 examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
 delete mode 100644 examples-testing/examples/webgl_materials_displacementmap.ts
 delete mode 100644 examples-testing/examples/webgl_materials_envmaps.ts
 delete mode 100644 examples-testing/examples/webgl_materials_envmaps_exr.ts
 delete mode 100644 examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
 delete mode 100644 examples-testing/examples/webgl_materials_envmaps_hdr.ts
 delete mode 100644 examples-testing/examples/webgl_materials_modified.ts
 delete mode 100644 examples-testing/examples/webgl_materials_normalmap_object_space.ts
 delete mode 100644 examples-testing/examples/webgl_materials_physical_clearcoat.ts
 delete mode 100644 examples-testing/examples/webgl_materials_physical_transmission.ts
 delete mode 100644 examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_anisotropy.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_canvas.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_filters.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_manualmipmap.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_partialupdate.ts
 delete mode 100644 examples-testing/examples/webgl_materials_texture_rotation.ts
 delete mode 100644 examples-testing/examples/webgl_materials_toon.ts
 delete mode 100644 examples-testing/examples/webgl_materials_video.ts
 delete mode 100644 examples-testing/examples/webgl_materials_video_webcam.ts
 delete mode 100644 examples-testing/examples/webgl_materials_wireframe.ts
 delete mode 100644 examples-testing/examples/webgl_math_obb.ts
 delete mode 100644 examples-testing/examples/webgl_math_orientation_transform.ts
 delete mode 100644 examples-testing/examples/webgl_mesh_batch.ts
 delete mode 100644 examples-testing/examples/webgl_mirror.ts
 delete mode 100644 examples-testing/examples/webgl_modifier_edgesplit.ts
 delete mode 100644 examples-testing/examples/webgl_modifier_simplifier.ts
 delete mode 100644 examples-testing/examples/webgl_modifier_tessellation.ts
 delete mode 100644 examples-testing/examples/webgl_morphtargets.ts
 delete mode 100644 examples-testing/examples/webgl_morphtargets_face.ts
 delete mode 100644 examples-testing/examples/webgl_morphtargets_horse.ts
 delete mode 100644 examples-testing/examples/webgl_morphtargets_sphere.ts
 delete mode 100644 examples-testing/examples/webgl_multiple_elements.ts
 delete mode 100644 examples-testing/examples/webgl_multiple_rendertargets.ts
 delete mode 100644 examples-testing/examples/webgl_multiple_scenes_comparison.ts
 delete mode 100644 examples-testing/examples/webgl_multiple_views.ts
 delete mode 100644 examples-testing/examples/webgl_multisampled_renderbuffers.ts
 delete mode 100644 examples-testing/examples/webgl_panorama_cube.ts
 delete mode 100644 examples-testing/examples/webgl_panorama_equirectangular.ts
 delete mode 100644 examples-testing/examples/webgl_performance.ts
 delete mode 100644 examples-testing/examples/webgl_pmrem_test.ts
 delete mode 100644 examples-testing/examples/webgl_points_billboards.ts
 delete mode 100644 examples-testing/examples/webgl_points_sprites.ts
 delete mode 100644 examples-testing/examples/webgl_points_waves.ts
 delete mode 100644 examples-testing/examples/webgl_portal.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_advanced.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_afterimage.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_backgrounds.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_fxaa.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_glitch.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_godrays.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_gtao.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_masking.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_material_ao.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_outline.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_pixel.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_procedural.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_sao.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_smaa.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_sobel.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_ssaa.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_ssao.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_ssr.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_taa.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_transition.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
 delete mode 100644 examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
 delete mode 100644 examples-testing/examples/webgl_raycaster_sprite.ts
 delete mode 100644 examples-testing/examples/webgl_raycaster_texture.ts
 delete mode 100644 examples-testing/examples/webgl_raymarching_reflect.ts
 delete mode 100644 examples-testing/examples/webgl_read_float_buffer.ts
 delete mode 100644 examples-testing/examples/webgl_refraction.ts
 delete mode 100644 examples-testing/examples/webgl_rtt.ts
 delete mode 100644 examples-testing/examples/webgl_shader.ts
 delete mode 100644 examples-testing/examples/webgl_shader_lava.ts
 delete mode 100644 examples-testing/examples/webgl_shaders_ocean.ts
 delete mode 100644 examples-testing/examples/webgl_shaders_sky.ts
 delete mode 100644 examples-testing/examples/webgl_shadow_contact.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_csm.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_pcss.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_performance.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_pointlight.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_progressive.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_viewer.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmap_vsm.ts
 delete mode 100644 examples-testing/examples/webgl_shadowmesh.ts
 delete mode 100644 examples-testing/examples/webgl_simple_gi.ts
 delete mode 100644 examples-testing/examples/webgl_sprites.ts
 delete mode 100644 examples-testing/examples/webgl_test_memory.ts
 delete mode 100644 examples-testing/examples/webgl_test_memory2.ts
 delete mode 100644 examples-testing/examples/webgl_test_wide_gamut.ts
 delete mode 100644 examples-testing/examples/webgl_texture2darray_compressed.ts
 delete mode 100644 examples-testing/examples/webgl_texture2darray_layerupdate.ts
 delete mode 100644 examples-testing/examples/webgl_texture3d.ts
 delete mode 100644 examples-testing/examples/webgl_texture3d_partialupdate.ts
 delete mode 100644 examples-testing/examples/webgl_tonemapping.ts
 delete mode 100644 examples-testing/examples/webgl_ubo.ts
 delete mode 100644 examples-testing/examples/webgl_ubo_arrays.ts
 delete mode 100644 examples-testing/examples/webgl_video_kinect.ts
 delete mode 100644 examples-testing/examples/webgl_video_panorama_equirectangular.ts
 delete mode 100644 examples-testing/examples/webgl_volume_cloud.ts
 delete mode 100644 examples-testing/examples/webgl_volume_instancing.ts
 delete mode 100644 examples-testing/examples/webgl_volume_perlin.ts
 delete mode 100644 examples-testing/examples/webgl_water.ts
 delete mode 100644 examples-testing/examples/webgl_water_flowmap.ts
 delete mode 100644 examples-testing/examples/webgpu_backdrop_area.ts
 delete mode 100644 examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
 delete mode 100644 examples-testing/examples/webgpu_clearcoat.ts
 delete mode 100644 examples-testing/examples/webgpu_clipping.ts
 delete mode 100644 examples-testing/examples/webgpu_custom_fog_background.ts
 delete mode 100644 examples-testing/examples/webgpu_display_stereo.ts
 delete mode 100644 examples-testing/examples/webgpu_instancing_morph.ts
 delete mode 100644 examples-testing/examples/webgpu_lightprobe.ts
 delete mode 100644 examples-testing/examples/webgpu_lights_ies_spotlight.ts
 delete mode 100644 examples-testing/examples/webgpu_lights_rectarealight.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_compressed.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_dispersion.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_iridescence.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_sheen.ts
 delete mode 100644 examples-testing/examples/webgpu_loader_gltf_transmission.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_basic.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_displacementmap.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_envmaps.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_lightmap.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_toon.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_transmission.ts
 delete mode 100644 examples-testing/examples/webgpu_materials_video.ts
 delete mode 100644 examples-testing/examples/webgpu_mesh_batch.ts
 delete mode 100644 examples-testing/examples/webgpu_morphtargets.ts
 delete mode 100644 examples-testing/examples/webgpu_morphtargets_face.ts
 delete mode 100644 examples-testing/examples/webgpu_mrt.ts
 delete mode 100644 examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
 delete mode 100644 examples-testing/examples/webgpu_ocean.ts
 delete mode 100644 examples-testing/examples/webgpu_parallax_uv.ts
 delete mode 100644 examples-testing/examples/webgpu_performance.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_3dlut.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_afterimage.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_ao.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_bloom.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_difference.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_dof.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_fxaa.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_masking.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_motion_blur.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_pixel.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_sobel.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_ssaa.ts
 delete mode 100644 examples-testing/examples/webgpu_postprocessing_transition.ts
 delete mode 100644 examples-testing/examples/webgpu_procedural_texture.ts
 delete mode 100644 examples-testing/examples/webgpu_refraction.ts
 delete mode 100644 examples-testing/examples/webgpu_sky.ts
 delete mode 100644 examples-testing/examples/webgpu_texture2darray_compressed.ts
 delete mode 100644 examples-testing/examples/webgpu_textures_anisotropy.ts
 delete mode 100644 examples-testing/examples/webgpu_textures_partialupdate.ts
 delete mode 100644 examples-testing/examples/webgpu_tsl_coffee_smoke.ts
 delete mode 100644 examples-testing/examples/webgpu_tsl_vfx_flames.ts
 delete mode 100644 examples-testing/examples/webgpu_video_panorama.ts
 delete mode 100644 examples-testing/examples/webgpu_water.ts
 delete mode 100644 examples-testing/examples/webxr_ar_cones.ts
 delete mode 100644 examples-testing/examples/webxr_ar_hittest.ts
 delete mode 100644 examples-testing/examples/webxr_ar_lighting.ts
 delete mode 100644 examples-testing/examples/webxr_ar_plane_detection.ts
 delete mode 100644 examples-testing/examples/webxr_vr_handinput.ts
 delete mode 100644 examples-testing/examples/webxr_vr_panorama.ts
 delete mode 100644 examples-testing/examples/webxr_vr_panorama_depth.ts
 delete mode 100644 examples-testing/examples/webxr_vr_rollercoaster.ts
 delete mode 100644 examples-testing/examples/webxr_vr_sandbox.ts
 delete mode 100644 examples-testing/examples/webxr_vr_video.ts
 delete mode 100644 examples-testing/examples/webxr_xr_controls_transform.ts
 delete mode 100644 examples-testing/examples/webxr_xr_dragging_custom_depth.ts

diff --git a/examples-testing/examples/css2d_label.ts b/examples-testing/examples/css2d_label.ts
deleted file mode 100644
index 48a2d1f05..000000000
--- a/examples-testing/examples/css2d_label.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let gui;
-
-let camera, scene, renderer, labelRenderer;
-
-const layers = {
-    'Toggle Name': function () {
-        camera.layers.toggle(0);
-    },
-    'Toggle Mass': function () {
-        camera.layers.toggle(1);
-    },
-    'Enable All': function () {
-        camera.layers.enableAll();
-    },
-
-    'Disable All': function () {
-        camera.layers.disableAll();
-    },
-};
-
-const clock = new THREE.Clock();
-const textureLoader = new THREE.TextureLoader();
-
-let moon;
-
-init();
-animate();
-
-function init() {
-    const EARTH_RADIUS = 1;
-    const MOON_RADIUS = 0.27;
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
-    camera.position.set(10, 5, 20);
-    camera.layers.enableAll();
-
-    scene = new THREE.Scene();
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(0, 0, 1);
-    dirLight.layers.enableAll();
-    scene.add(dirLight);
-
-    const axesHelper = new THREE.AxesHelper(5);
-    axesHelper.layers.enableAll();
-    scene.add(axesHelper);
-
-    //
-
-    const earthGeometry = new THREE.SphereGeometry(EARTH_RADIUS, 16, 16);
-    const earthMaterial = new THREE.MeshPhongMaterial({
-        specular: 0x333333,
-        shininess: 5,
-        map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),
-        specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),
-        normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),
-        normalScale: new THREE.Vector2(0.85, 0.85),
-    });
-    earthMaterial.map.colorSpace = THREE.SRGBColorSpace;
-    const earth = new THREE.Mesh(earthGeometry, earthMaterial);
-    scene.add(earth);
-
-    const moonGeometry = new THREE.SphereGeometry(MOON_RADIUS, 16, 16);
-    const moonMaterial = new THREE.MeshPhongMaterial({
-        shininess: 5,
-        map: textureLoader.load('textures/planets/moon_1024.jpg'),
-    });
-    moonMaterial.map.colorSpace = THREE.SRGBColorSpace;
-    moon = new THREE.Mesh(moonGeometry, moonMaterial);
-    scene.add(moon);
-
-    //
-
-    earth.layers.enableAll();
-    moon.layers.enableAll();
-
-    const earthDiv = document.createElement('div');
-    earthDiv.className = 'label';
-    earthDiv.textContent = 'Earth';
-    earthDiv.style.backgroundColor = 'transparent';
-
-    const earthLabel = new CSS2DObject(earthDiv);
-    earthLabel.position.set(1.5 * EARTH_RADIUS, 0, 0);
-    earthLabel.center.set(0, 1);
-    earth.add(earthLabel);
-    earthLabel.layers.set(0);
-
-    const earthMassDiv = document.createElement('div');
-    earthMassDiv.className = 'label';
-    earthMassDiv.textContent = '5.97237e24 kg';
-    earthMassDiv.style.backgroundColor = 'transparent';
-
-    const earthMassLabel = new CSS2DObject(earthMassDiv);
-    earthMassLabel.position.set(1.5 * EARTH_RADIUS, 0, 0);
-    earthMassLabel.center.set(0, 0);
-    earth.add(earthMassLabel);
-    earthMassLabel.layers.set(1);
-
-    const moonDiv = document.createElement('div');
-    moonDiv.className = 'label';
-    moonDiv.textContent = 'Moon';
-    moonDiv.style.backgroundColor = 'transparent';
-
-    const moonLabel = new CSS2DObject(moonDiv);
-    moonLabel.position.set(1.5 * MOON_RADIUS, 0, 0);
-    moonLabel.center.set(0, 1);
-    moon.add(moonLabel);
-    moonLabel.layers.set(0);
-
-    const moonMassDiv = document.createElement('div');
-    moonMassDiv.className = 'label';
-    moonMassDiv.textContent = '7.342e22 kg';
-    moonMassDiv.style.backgroundColor = 'transparent';
-
-    const moonMassLabel = new CSS2DObject(moonMassDiv);
-    moonMassLabel.position.set(1.5 * MOON_RADIUS, 0, 0);
-    moonMassLabel.center.set(0, 0);
-    moon.add(moonMassLabel);
-    moonMassLabel.layers.set(1);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    labelRenderer = new CSS2DRenderer();
-    labelRenderer.setSize(window.innerWidth, window.innerHeight);
-    labelRenderer.domElement.style.position = 'absolute';
-    labelRenderer.domElement.style.top = '0px';
-    document.body.appendChild(labelRenderer.domElement);
-
-    const controls = new OrbitControls(camera, labelRenderer.domElement);
-    controls.minDistance = 5;
-    controls.maxDistance = 100;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    initGui();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    labelRenderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    const elapsed = clock.getElapsedTime();
-
-    moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5);
-
-    renderer.render(scene, camera);
-    labelRenderer.render(scene, camera);
-}
-
-//
-
-function initGui() {
-    gui = new GUI();
-
-    gui.title('Camera Layers');
-
-    gui.add(layers, 'Toggle Name');
-    gui.add(layers, 'Toggle Mass');
-    gui.add(layers, 'Enable All');
-    gui.add(layers, 'Disable All');
-
-    gui.open();
-}
diff --git a/examples-testing/examples/css3d_molecules.ts b/examples-testing/examples/css3d_molecules.ts
deleted file mode 100644
index 538472607..000000000
--- a/examples-testing/examples/css3d_molecules.ts
+++ /dev/null
@@ -1,353 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';
-import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-let controls;
-let root;
-
-const objects = [];
-const tmpVec1 = new THREE.Vector3();
-const tmpVec2 = new THREE.Vector3();
-const tmpVec3 = new THREE.Vector3();
-const tmpVec4 = new THREE.Vector3();
-const offset = new THREE.Vector3();
-
-const VIZ_TYPE = {
-    Atoms: 0,
-    Bonds: 1,
-    'Atoms + Bonds': 2,
-};
-
-const MOLECULES = {
-    Ethanol: 'ethanol.pdb',
-    Aspirin: 'aspirin.pdb',
-    Caffeine: 'caffeine.pdb',
-    Nicotine: 'nicotine.pdb',
-    LSD: 'lsd.pdb',
-    Cocaine: 'cocaine.pdb',
-    Cholesterol: 'cholesterol.pdb',
-    Lycopene: 'lycopene.pdb',
-    Glucose: 'glucose.pdb',
-    'Aluminium oxide': 'Al2O3.pdb',
-    Cubane: 'cubane.pdb',
-    Copper: 'cu.pdb',
-    Fluorite: 'caf2.pdb',
-    Salt: 'nacl.pdb',
-    'YBCO superconductor': 'ybco.pdb',
-    Buckyball: 'buckyball.pdb',
-    // 'Diamond': 'diamond.pdb',
-    Graphite: 'graphite.pdb',
-};
-
-const params = {
-    vizType: 2,
-    molecule: 'caffeine.pdb',
-};
-
-const loader = new PDBLoader();
-const colorSpriteMap = {};
-const baseSprite = document.createElement('img');
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-
-    root = new THREE.Object3D();
-    scene.add(root);
-
-    //
-
-    renderer = new CSS3DRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.getElementById('container').appendChild(renderer.domElement);
-
-    //
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.rotateSpeed = 0.5;
-
-    //
-
-    baseSprite.onload = function () {
-        loadMolecule(params.molecule);
-    };
-
-    baseSprite.src = 'textures/sprites/ball.png';
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'vizType', VIZ_TYPE).onChange(changeVizType);
-    gui.add(params, 'molecule', MOLECULES).onChange(loadMolecule);
-    gui.open();
-}
-
-function changeVizType(value) {
-    if (value === 0) showAtoms();
-    else if (value === 1) showBonds();
-    else showAtomsBonds();
-}
-
-//
-
-function showAtoms() {
-    for (let i = 0; i < objects.length; i++) {
-        const object = objects[i];
-
-        if (object instanceof CSS3DSprite) {
-            object.element.style.display = '';
-            object.visible = true;
-        } else {
-            object.element.style.display = 'none';
-            object.visible = false;
-        }
-    }
-}
-
-function showBonds() {
-    for (let i = 0; i < objects.length; i++) {
-        const object = objects[i];
-
-        if (object instanceof CSS3DSprite) {
-            object.element.style.display = 'none';
-            object.visible = false;
-        } else {
-            object.element.style.display = '';
-            object.element.style.height = object.userData.bondLengthFull;
-            object.visible = true;
-        }
-    }
-}
-
-function showAtomsBonds() {
-    for (let i = 0; i < objects.length; i++) {
-        const object = objects[i];
-
-        object.element.style.display = '';
-        object.visible = true;
-
-        if (!(object instanceof CSS3DSprite)) {
-            object.element.style.height = object.userData.bondLengthShort;
-        }
-    }
-}
-
-//
-
-function colorify(ctx, width, height, color) {
-    const r = color.r,
-        g = color.g,
-        b = color.b;
-
-    const imageData = ctx.getImageData(0, 0, width, height);
-    const data = imageData.data;
-
-    for (let i = 0, l = data.length; i < l; i += 4) {
-        data[i + 0] *= r;
-        data[i + 1] *= g;
-        data[i + 2] *= b;
-    }
-
-    ctx.putImageData(imageData, 0, 0);
-}
-
-function imageToCanvas(image) {
-    const width = image.width;
-    const height = image.height;
-
-    const canvas = document.createElement('canvas');
-
-    canvas.width = width;
-    canvas.height = height;
-
-    const context = canvas.getContext('2d');
-    context.drawImage(image, 0, 0, width, height);
-
-    return canvas;
-}
-
-//
-
-function loadMolecule(model) {
-    const url = 'models/pdb/' + model;
-
-    for (let i = 0; i < objects.length; i++) {
-        const object = objects[i];
-        object.parent.remove(object);
-    }
-
-    objects.length = 0;
-
-    loader.load(url, function (pdb) {
-        const geometryAtoms = pdb.geometryAtoms;
-        const geometryBonds = pdb.geometryBonds;
-        const json = pdb.json;
-
-        geometryAtoms.computeBoundingBox();
-        geometryAtoms.boundingBox.getCenter(offset).negate();
-
-        geometryAtoms.translate(offset.x, offset.y, offset.z);
-        geometryBonds.translate(offset.x, offset.y, offset.z);
-
-        const positionAtoms = geometryAtoms.getAttribute('position');
-        const colorAtoms = geometryAtoms.getAttribute('color');
-
-        const position = new THREE.Vector3();
-        const color = new THREE.Color();
-
-        for (let i = 0; i < positionAtoms.count; i++) {
-            position.fromBufferAttribute(positionAtoms, i);
-            color.fromBufferAttribute(colorAtoms, i);
-
-            const atomJSON = json.atoms[i];
-            const element = atomJSON[4];
-
-            if (!colorSpriteMap[element]) {
-                const canvas = imageToCanvas(baseSprite);
-                const context = canvas.getContext('2d');
-
-                colorify(context, canvas.width, canvas.height, color);
-
-                const dataUrl = canvas.toDataURL();
-
-                colorSpriteMap[element] = dataUrl;
-            }
-
-            const colorSprite = colorSpriteMap[element];
-
-            const atom = document.createElement('img');
-            atom.src = colorSprite;
-
-            const object = new CSS3DSprite(atom);
-            object.position.copy(position);
-            object.position.multiplyScalar(75);
-
-            object.matrixAutoUpdate = false;
-            object.updateMatrix();
-
-            root.add(object);
-
-            objects.push(object);
-        }
-
-        const positionBonds = geometryBonds.getAttribute('position');
-
-        const start = new THREE.Vector3();
-        const end = new THREE.Vector3();
-
-        for (let i = 0; i < positionBonds.count; i += 2) {
-            start.fromBufferAttribute(positionBonds, i);
-            end.fromBufferAttribute(positionBonds, i + 1);
-
-            start.multiplyScalar(75);
-            end.multiplyScalar(75);
-
-            tmpVec1.subVectors(end, start);
-            const bondLength = tmpVec1.length() - 50;
-
-            //
-
-            let bond = document.createElement('div');
-            bond.className = 'bond';
-            bond.style.height = bondLength + 'px';
-
-            let object = new CSS3DObject(bond);
-            object.position.copy(start);
-            object.position.lerp(end, 0.5);
-
-            object.userData.bondLengthShort = bondLength + 'px';
-            object.userData.bondLengthFull = bondLength + 55 + 'px';
-
-            //
-
-            const axis = tmpVec2.set(0, 1, 0).cross(tmpVec1);
-            const radians = Math.acos(tmpVec3.set(0, 1, 0).dot(tmpVec4.copy(tmpVec1).normalize()));
-
-            const objMatrix = new THREE.Matrix4().makeRotationAxis(axis.normalize(), radians);
-            object.matrix.copy(objMatrix);
-            object.quaternion.setFromRotationMatrix(object.matrix);
-
-            object.matrixAutoUpdate = false;
-            object.updateMatrix();
-
-            root.add(object);
-
-            objects.push(object);
-
-            //
-
-            const joint = new THREE.Object3D();
-            joint.position.copy(start);
-            joint.position.lerp(end, 0.5);
-
-            joint.matrix.copy(objMatrix);
-            joint.quaternion.setFromRotationMatrix(joint.matrix);
-
-            joint.matrixAutoUpdate = false;
-            joint.updateMatrix();
-
-            bond = document.createElement('div');
-            bond.className = 'bond';
-            bond.style.height = bondLength + 'px';
-
-            object = new CSS3DObject(bond);
-            object.rotation.y = Math.PI / 2;
-
-            object.matrixAutoUpdate = false;
-            object.updateMatrix();
-
-            object.userData.bondLengthShort = bondLength + 'px';
-            object.userData.bondLengthFull = bondLength + 55 + 'px';
-
-            object.userData.joint = joint;
-
-            joint.add(object);
-            root.add(joint);
-
-            objects.push(object);
-        }
-
-        //console.log( "CSS3DObjects:", objects.length );
-
-        changeVizType(params.vizType);
-    });
-}
-
-//
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-    controls.update();
-
-    const time = Date.now() * 0.0004;
-
-    root.rotation.x = time;
-    root.rotation.y = time * 0.7;
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/css3d_orthographic.ts b/examples-testing/examples/css3d_orthographic.ts
deleted file mode 100644
index 4aabbed08..000000000
--- a/examples-testing/examples/css3d_orthographic.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-let scene2, renderer2;
-
-const frustumSize = 500;
-
-init();
-animate();
-
-function init() {
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera(
-        (frustumSize * aspect) / -2,
-        (frustumSize * aspect) / 2,
-        frustumSize / 2,
-        frustumSize / -2,
-        1,
-        1000,
-    );
-
-    camera.position.set(-200, 200, 200);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    scene2 = new THREE.Scene();
-
-    const material = new THREE.MeshBasicMaterial({
-        color: 0x000000,
-        wireframe: true,
-        wireframeLinewidth: 1,
-        side: THREE.DoubleSide,
-    });
-
-    // left
-    createPlane(
-        100,
-        100,
-        'chocolate',
-        new THREE.Vector3(-50, 0, 0),
-        new THREE.Euler(0, -90 * THREE.MathUtils.DEG2RAD, 0),
-    );
-    // right
-    createPlane(100, 100, 'saddlebrown', new THREE.Vector3(0, 0, 50), new THREE.Euler(0, 0, 0));
-    // top
-    createPlane(
-        100,
-        100,
-        'yellowgreen',
-        new THREE.Vector3(0, 50, 0),
-        new THREE.Euler(-90 * THREE.MathUtils.DEG2RAD, 0, 0),
-    );
-    // bottom
-    createPlane(
-        300,
-        300,
-        'seagreen',
-        new THREE.Vector3(0, -50, 0),
-        new THREE.Euler(-90 * THREE.MathUtils.DEG2RAD, 0, 0),
-    );
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    renderer2 = new CSS3DRenderer();
-    renderer2.setSize(window.innerWidth, window.innerHeight);
-    renderer2.domElement.style.position = 'absolute';
-    renderer2.domElement.style.top = 0;
-    document.body.appendChild(renderer2.domElement);
-
-    const controls = new OrbitControls(camera, renderer2.domElement);
-    controls.minZoom = 0.5;
-    controls.maxZoom = 2;
-
-    function createPlane(width, height, cssColor, pos, rot) {
-        const element = document.createElement('div');
-        element.style.width = width + 'px';
-        element.style.height = height + 'px';
-        element.style.opacity = 0.75;
-        element.style.background = cssColor;
-
-        const object = new CSS3DObject(element);
-        object.position.copy(pos);
-        object.rotation.copy(rot);
-        scene2.add(object);
-
-        const geometry = new THREE.PlaneGeometry(width, height);
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.copy(object.position);
-        mesh.rotation.copy(object.rotation);
-        scene.add(mesh);
-    }
-
-    window.addEventListener('resize', onWindowResize);
-    createPanel();
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera.left = (-frustumSize * aspect) / 2;
-    camera.right = (frustumSize * aspect) / 2;
-    camera.top = frustumSize / 2;
-    camera.bottom = -frustumSize / 2;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    renderer2.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    renderer.render(scene, camera);
-    renderer2.render(scene2, camera);
-}
-
-function createPanel() {
-    const panel = new GUI();
-    const folder1 = panel.addFolder('camera setViewOffset').close();
-
-    const settings = {
-        setViewOffset() {
-            folder1.children[1].enable().setValue(window.innerWidth);
-            folder1.children[2].enable().setValue(window.innerHeight);
-            folder1.children[3].enable().setValue(0);
-            folder1.children[4].enable().setValue(0);
-            folder1.children[5].enable().setValue(window.innerWidth);
-            folder1.children[6].enable().setValue(window.innerHeight);
-        },
-        fullWidth: 0,
-        fullHeight: 0,
-        offsetX: 0,
-        offsetY: 0,
-        width: 0,
-        height: 0,
-        clearViewOffset() {
-            folder1.children[1].setValue(0).disable();
-            folder1.children[2].setValue(0).disable();
-            folder1.children[3].setValue(0).disable();
-            folder1.children[4].setValue(0).disable();
-            folder1.children[5].setValue(0).disable();
-            folder1.children[6].setValue(0).disable();
-            camera.clearViewOffset();
-        },
-    };
-
-    folder1.add(settings, 'setViewOffset');
-    folder1
-        .add(settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1)
-        .onChange(val => updateCameraViewOffset({ fullWidth: val }))
-        .disable();
-    folder1
-        .add(settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1)
-        .onChange(val => updateCameraViewOffset({ fullHeight: val }))
-        .disable();
-    folder1
-        .add(settings, 'offsetX', 0, 256, 1)
-        .onChange(val => updateCameraViewOffset({ x: val }))
-        .disable();
-    folder1
-        .add(settings, 'offsetY', 0, 256, 1)
-        .onChange(val => updateCameraViewOffset({ y: val }))
-        .disable();
-    folder1
-        .add(settings, 'width', window.screen.width / 4, window.screen.width * 2, 1)
-        .onChange(val => updateCameraViewOffset({ width: val }))
-        .disable();
-    folder1
-        .add(settings, 'height', window.screen.height / 4, window.screen.height * 2, 1)
-        .onChange(val => updateCameraViewOffset({ height: val }))
-        .disable();
-    folder1.add(settings, 'clearViewOffset');
-}
-
-function updateCameraViewOffset({ fullWidth, fullHeight, x, y, width, height }) {
-    if (!camera.view) {
-        camera.setViewOffset(
-            fullWidth || window.innerWidth,
-            fullHeight || window.innerHeight,
-            x || 0,
-            y || 0,
-            width || window.innerWidth,
-            height || window.innerHeight,
-        );
-    } else {
-        camera.setViewOffset(
-            fullWidth || camera.view.fullWidth,
-            fullHeight || camera.view.fullHeight,
-            x || camera.view.offsetX,
-            y || camera.view.offsetY,
-            width || camera.view.width,
-            height || camera.view.height,
-        );
-    }
-}
diff --git a/examples-testing/examples/css3d_periodictable.ts b/examples-testing/examples/css3d_periodictable.ts
deleted file mode 100644
index e3a33f796..000000000
--- a/examples-testing/examples/css3d_periodictable.ts
+++ /dev/null
@@ -1,793 +0,0 @@
-import * as THREE from 'three';
-
-import TWEEN from 'three/addons/libs/tween.module.js';
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
-
-const table = [
-    'H',
-    'Hydrogen',
-    '1.00794',
-    1,
-    1,
-    'He',
-    'Helium',
-    '4.002602',
-    18,
-    1,
-    'Li',
-    'Lithium',
-    '6.941',
-    1,
-    2,
-    'Be',
-    'Beryllium',
-    '9.012182',
-    2,
-    2,
-    'B',
-    'Boron',
-    '10.811',
-    13,
-    2,
-    'C',
-    'Carbon',
-    '12.0107',
-    14,
-    2,
-    'N',
-    'Nitrogen',
-    '14.0067',
-    15,
-    2,
-    'O',
-    'Oxygen',
-    '15.9994',
-    16,
-    2,
-    'F',
-    'Fluorine',
-    '18.9984032',
-    17,
-    2,
-    'Ne',
-    'Neon',
-    '20.1797',
-    18,
-    2,
-    'Na',
-    'Sodium',
-    '22.98976...',
-    1,
-    3,
-    'Mg',
-    'Magnesium',
-    '24.305',
-    2,
-    3,
-    'Al',
-    'Aluminium',
-    '26.9815386',
-    13,
-    3,
-    'Si',
-    'Silicon',
-    '28.0855',
-    14,
-    3,
-    'P',
-    'Phosphorus',
-    '30.973762',
-    15,
-    3,
-    'S',
-    'Sulfur',
-    '32.065',
-    16,
-    3,
-    'Cl',
-    'Chlorine',
-    '35.453',
-    17,
-    3,
-    'Ar',
-    'Argon',
-    '39.948',
-    18,
-    3,
-    'K',
-    'Potassium',
-    '39.948',
-    1,
-    4,
-    'Ca',
-    'Calcium',
-    '40.078',
-    2,
-    4,
-    'Sc',
-    'Scandium',
-    '44.955912',
-    3,
-    4,
-    'Ti',
-    'Titanium',
-    '47.867',
-    4,
-    4,
-    'V',
-    'Vanadium',
-    '50.9415',
-    5,
-    4,
-    'Cr',
-    'Chromium',
-    '51.9961',
-    6,
-    4,
-    'Mn',
-    'Manganese',
-    '54.938045',
-    7,
-    4,
-    'Fe',
-    'Iron',
-    '55.845',
-    8,
-    4,
-    'Co',
-    'Cobalt',
-    '58.933195',
-    9,
-    4,
-    'Ni',
-    'Nickel',
-    '58.6934',
-    10,
-    4,
-    'Cu',
-    'Copper',
-    '63.546',
-    11,
-    4,
-    'Zn',
-    'Zinc',
-    '65.38',
-    12,
-    4,
-    'Ga',
-    'Gallium',
-    '69.723',
-    13,
-    4,
-    'Ge',
-    'Germanium',
-    '72.63',
-    14,
-    4,
-    'As',
-    'Arsenic',
-    '74.9216',
-    15,
-    4,
-    'Se',
-    'Selenium',
-    '78.96',
-    16,
-    4,
-    'Br',
-    'Bromine',
-    '79.904',
-    17,
-    4,
-    'Kr',
-    'Krypton',
-    '83.798',
-    18,
-    4,
-    'Rb',
-    'Rubidium',
-    '85.4678',
-    1,
-    5,
-    'Sr',
-    'Strontium',
-    '87.62',
-    2,
-    5,
-    'Y',
-    'Yttrium',
-    '88.90585',
-    3,
-    5,
-    'Zr',
-    'Zirconium',
-    '91.224',
-    4,
-    5,
-    'Nb',
-    'Niobium',
-    '92.90628',
-    5,
-    5,
-    'Mo',
-    'Molybdenum',
-    '95.96',
-    6,
-    5,
-    'Tc',
-    'Technetium',
-    '(98)',
-    7,
-    5,
-    'Ru',
-    'Ruthenium',
-    '101.07',
-    8,
-    5,
-    'Rh',
-    'Rhodium',
-    '102.9055',
-    9,
-    5,
-    'Pd',
-    'Palladium',
-    '106.42',
-    10,
-    5,
-    'Ag',
-    'Silver',
-    '107.8682',
-    11,
-    5,
-    'Cd',
-    'Cadmium',
-    '112.411',
-    12,
-    5,
-    'In',
-    'Indium',
-    '114.818',
-    13,
-    5,
-    'Sn',
-    'Tin',
-    '118.71',
-    14,
-    5,
-    'Sb',
-    'Antimony',
-    '121.76',
-    15,
-    5,
-    'Te',
-    'Tellurium',
-    '127.6',
-    16,
-    5,
-    'I',
-    'Iodine',
-    '126.90447',
-    17,
-    5,
-    'Xe',
-    'Xenon',
-    '131.293',
-    18,
-    5,
-    'Cs',
-    'Caesium',
-    '132.9054',
-    1,
-    6,
-    'Ba',
-    'Barium',
-    '132.9054',
-    2,
-    6,
-    'La',
-    'Lanthanum',
-    '138.90547',
-    4,
-    9,
-    'Ce',
-    'Cerium',
-    '140.116',
-    5,
-    9,
-    'Pr',
-    'Praseodymium',
-    '140.90765',
-    6,
-    9,
-    'Nd',
-    'Neodymium',
-    '144.242',
-    7,
-    9,
-    'Pm',
-    'Promethium',
-    '(145)',
-    8,
-    9,
-    'Sm',
-    'Samarium',
-    '150.36',
-    9,
-    9,
-    'Eu',
-    'Europium',
-    '151.964',
-    10,
-    9,
-    'Gd',
-    'Gadolinium',
-    '157.25',
-    11,
-    9,
-    'Tb',
-    'Terbium',
-    '158.92535',
-    12,
-    9,
-    'Dy',
-    'Dysprosium',
-    '162.5',
-    13,
-    9,
-    'Ho',
-    'Holmium',
-    '164.93032',
-    14,
-    9,
-    'Er',
-    'Erbium',
-    '167.259',
-    15,
-    9,
-    'Tm',
-    'Thulium',
-    '168.93421',
-    16,
-    9,
-    'Yb',
-    'Ytterbium',
-    '173.054',
-    17,
-    9,
-    'Lu',
-    'Lutetium',
-    '174.9668',
-    18,
-    9,
-    'Hf',
-    'Hafnium',
-    '178.49',
-    4,
-    6,
-    'Ta',
-    'Tantalum',
-    '180.94788',
-    5,
-    6,
-    'W',
-    'Tungsten',
-    '183.84',
-    6,
-    6,
-    'Re',
-    'Rhenium',
-    '186.207',
-    7,
-    6,
-    'Os',
-    'Osmium',
-    '190.23',
-    8,
-    6,
-    'Ir',
-    'Iridium',
-    '192.217',
-    9,
-    6,
-    'Pt',
-    'Platinum',
-    '195.084',
-    10,
-    6,
-    'Au',
-    'Gold',
-    '196.966569',
-    11,
-    6,
-    'Hg',
-    'Mercury',
-    '200.59',
-    12,
-    6,
-    'Tl',
-    'Thallium',
-    '204.3833',
-    13,
-    6,
-    'Pb',
-    'Lead',
-    '207.2',
-    14,
-    6,
-    'Bi',
-    'Bismuth',
-    '208.9804',
-    15,
-    6,
-    'Po',
-    'Polonium',
-    '(209)',
-    16,
-    6,
-    'At',
-    'Astatine',
-    '(210)',
-    17,
-    6,
-    'Rn',
-    'Radon',
-    '(222)',
-    18,
-    6,
-    'Fr',
-    'Francium',
-    '(223)',
-    1,
-    7,
-    'Ra',
-    'Radium',
-    '(226)',
-    2,
-    7,
-    'Ac',
-    'Actinium',
-    '(227)',
-    4,
-    10,
-    'Th',
-    'Thorium',
-    '232.03806',
-    5,
-    10,
-    'Pa',
-    'Protactinium',
-    '231.0588',
-    6,
-    10,
-    'U',
-    'Uranium',
-    '238.02891',
-    7,
-    10,
-    'Np',
-    'Neptunium',
-    '(237)',
-    8,
-    10,
-    'Pu',
-    'Plutonium',
-    '(244)',
-    9,
-    10,
-    'Am',
-    'Americium',
-    '(243)',
-    10,
-    10,
-    'Cm',
-    'Curium',
-    '(247)',
-    11,
-    10,
-    'Bk',
-    'Berkelium',
-    '(247)',
-    12,
-    10,
-    'Cf',
-    'Californium',
-    '(251)',
-    13,
-    10,
-    'Es',
-    'Einstenium',
-    '(252)',
-    14,
-    10,
-    'Fm',
-    'Fermium',
-    '(257)',
-    15,
-    10,
-    'Md',
-    'Mendelevium',
-    '(258)',
-    16,
-    10,
-    'No',
-    'Nobelium',
-    '(259)',
-    17,
-    10,
-    'Lr',
-    'Lawrencium',
-    '(262)',
-    18,
-    10,
-    'Rf',
-    'Rutherfordium',
-    '(267)',
-    4,
-    7,
-    'Db',
-    'Dubnium',
-    '(268)',
-    5,
-    7,
-    'Sg',
-    'Seaborgium',
-    '(271)',
-    6,
-    7,
-    'Bh',
-    'Bohrium',
-    '(272)',
-    7,
-    7,
-    'Hs',
-    'Hassium',
-    '(270)',
-    8,
-    7,
-    'Mt',
-    'Meitnerium',
-    '(276)',
-    9,
-    7,
-    'Ds',
-    'Darmstadium',
-    '(281)',
-    10,
-    7,
-    'Rg',
-    'Roentgenium',
-    '(280)',
-    11,
-    7,
-    'Cn',
-    'Copernicium',
-    '(285)',
-    12,
-    7,
-    'Nh',
-    'Nihonium',
-    '(286)',
-    13,
-    7,
-    'Fl',
-    'Flerovium',
-    '(289)',
-    14,
-    7,
-    'Mc',
-    'Moscovium',
-    '(290)',
-    15,
-    7,
-    'Lv',
-    'Livermorium',
-    '(293)',
-    16,
-    7,
-    'Ts',
-    'Tennessine',
-    '(294)',
-    17,
-    7,
-    'Og',
-    'Oganesson',
-    '(294)',
-    18,
-    7,
-];
-
-let camera, scene, renderer;
-let controls;
-
-const objects = [];
-const targets = { table: [], sphere: [], helix: [], grid: [] };
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 3000;
-
-    scene = new THREE.Scene();
-
-    // table
-
-    for (let i = 0; i < table.length; i += 5) {
-        const element = document.createElement('div');
-        element.className = 'element';
-        element.style.backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')';
-
-        const number = document.createElement('div');
-        number.className = 'number';
-        number.textContent = i / 5 + 1;
-        element.appendChild(number);
-
-        const symbol = document.createElement('div');
-        symbol.className = 'symbol';
-        symbol.textContent = table[i];
-        element.appendChild(symbol);
-
-        const details = document.createElement('div');
-        details.className = 'details';
-        details.innerHTML = table[i + 1] + '<br>' + table[i + 2];
-        element.appendChild(details);
-
-        const objectCSS = new CSS3DObject(element);
-        objectCSS.position.x = Math.random() * 4000 - 2000;
-        objectCSS.position.y = Math.random() * 4000 - 2000;
-        objectCSS.position.z = Math.random() * 4000 - 2000;
-        scene.add(objectCSS);
-
-        objects.push(objectCSS);
-
-        //
-
-        const object = new THREE.Object3D();
-        object.position.x = table[i + 3] * 140 - 1330;
-        object.position.y = -(table[i + 4] * 180) + 990;
-
-        targets.table.push(object);
-    }
-
-    // sphere
-
-    const vector = new THREE.Vector3();
-
-    for (let i = 0, l = objects.length; i < l; i++) {
-        const phi = Math.acos(-1 + (2 * i) / l);
-        const theta = Math.sqrt(l * Math.PI) * phi;
-
-        const object = new THREE.Object3D();
-
-        object.position.setFromSphericalCoords(800, phi, theta);
-
-        vector.copy(object.position).multiplyScalar(2);
-
-        object.lookAt(vector);
-
-        targets.sphere.push(object);
-    }
-
-    // helix
-
-    for (let i = 0, l = objects.length; i < l; i++) {
-        const theta = i * 0.175 + Math.PI;
-        const y = -(i * 8) + 450;
-
-        const object = new THREE.Object3D();
-
-        object.position.setFromCylindricalCoords(900, theta, y);
-
-        vector.x = object.position.x * 2;
-        vector.y = object.position.y;
-        vector.z = object.position.z * 2;
-
-        object.lookAt(vector);
-
-        targets.helix.push(object);
-    }
-
-    // grid
-
-    for (let i = 0; i < objects.length; i++) {
-        const object = new THREE.Object3D();
-
-        object.position.x = (i % 5) * 400 - 800;
-        object.position.y = -(Math.floor(i / 5) % 5) * 400 + 800;
-        object.position.z = Math.floor(i / 25) * 1000 - 2000;
-
-        targets.grid.push(object);
-    }
-
-    //
-
-    renderer = new CSS3DRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.getElementById('container').appendChild(renderer.domElement);
-
-    //
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.minDistance = 500;
-    controls.maxDistance = 6000;
-    controls.addEventListener('change', render);
-
-    const buttonTable = document.getElementById('table');
-    buttonTable.addEventListener('click', function () {
-        transform(targets.table, 2000);
-    });
-
-    const buttonSphere = document.getElementById('sphere');
-    buttonSphere.addEventListener('click', function () {
-        transform(targets.sphere, 2000);
-    });
-
-    const buttonHelix = document.getElementById('helix');
-    buttonHelix.addEventListener('click', function () {
-        transform(targets.helix, 2000);
-    });
-
-    const buttonGrid = document.getElementById('grid');
-    buttonGrid.addEventListener('click', function () {
-        transform(targets.grid, 2000);
-    });
-
-    transform(targets.table, 2000);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function transform(targets, duration) {
-    TWEEN.removeAll();
-
-    for (let i = 0; i < objects.length; i++) {
-        const object = objects[i];
-        const target = targets[i];
-
-        new TWEEN.Tween(object.position)
-            .to(
-                { x: target.position.x, y: target.position.y, z: target.position.z },
-                Math.random() * duration + duration,
-            )
-            .easing(TWEEN.Easing.Exponential.InOut)
-            .start();
-
-        new TWEEN.Tween(object.rotation)
-            .to(
-                { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z },
-                Math.random() * duration + duration,
-            )
-            .easing(TWEEN.Easing.Exponential.InOut)
-            .start();
-    }
-
-    new TWEEN.Tween(this)
-        .to({}, duration * 2)
-        .onUpdate(render)
-        .start();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    TWEEN.update();
-
-    controls.update();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/css3d_sandbox.ts b/examples-testing/examples/css3d_sandbox.ts
deleted file mode 100644
index 1088b84b1..000000000
--- a/examples-testing/examples/css3d_sandbox.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-let scene2, renderer2;
-
-let controls;
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(200, 200, 200);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    scene2 = new THREE.Scene();
-
-    const material = new THREE.MeshBasicMaterial({
-        color: 0x000000,
-        wireframe: true,
-        wireframeLinewidth: 1,
-        side: THREE.DoubleSide,
-    });
-
-    //
-
-    for (let i = 0; i < 10; i++) {
-        const element = document.createElement('div');
-        element.style.width = '100px';
-        element.style.height = '100px';
-        element.style.opacity = i < 5 ? 0.5 : 1;
-        element.style.background = new THREE.Color(Math.random() * 0xffffff).getStyle();
-
-        const object = new CSS3DObject(element);
-        object.position.x = Math.random() * 200 - 100;
-        object.position.y = Math.random() * 200 - 100;
-        object.position.z = Math.random() * 200 - 100;
-        object.rotation.x = Math.random();
-        object.rotation.y = Math.random();
-        object.rotation.z = Math.random();
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        scene2.add(object);
-
-        const geometry = new THREE.PlaneGeometry(100, 100);
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.copy(object.position);
-        mesh.rotation.copy(object.rotation);
-        mesh.scale.copy(object.scale);
-        scene.add(mesh);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    renderer2 = new CSS3DRenderer();
-    renderer2.setSize(window.innerWidth, window.innerHeight);
-    renderer2.domElement.style.position = 'absolute';
-    renderer2.domElement.style.top = 0;
-    document.body.appendChild(renderer2.domElement);
-
-    controls = new TrackballControls(camera, renderer2.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-    createPanel();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    renderer2.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    controls.update();
-
-    renderer.render(scene, camera);
-    renderer2.render(scene2, camera);
-}
-
-function createPanel() {
-    const panel = new GUI();
-    const folder1 = panel.addFolder('camera setViewOffset').close();
-
-    const settings = {
-        setViewOffset() {
-            folder1.children[1].enable().setValue(window.innerWidth);
-            folder1.children[2].enable().setValue(window.innerHeight);
-            folder1.children[3].enable().setValue(0);
-            folder1.children[4].enable().setValue(0);
-            folder1.children[5].enable().setValue(window.innerWidth);
-            folder1.children[6].enable().setValue(window.innerHeight);
-        },
-        fullWidth: 0,
-        fullHeight: 0,
-        offsetX: 0,
-        offsetY: 0,
-        width: 0,
-        height: 0,
-        clearViewOffset() {
-            folder1.children[1].setValue(0).disable();
-            folder1.children[2].setValue(0).disable();
-            folder1.children[3].setValue(0).disable();
-            folder1.children[4].setValue(0).disable();
-            folder1.children[5].setValue(0).disable();
-            folder1.children[6].setValue(0).disable();
-            camera.clearViewOffset();
-        },
-    };
-
-    folder1.add(settings, 'setViewOffset');
-    folder1
-        .add(settings, 'fullWidth', window.screen.width / 4, window.screen.width * 2, 1)
-        .onChange(val => updateCameraViewOffset({ fullWidth: val }))
-        .disable();
-    folder1
-        .add(settings, 'fullHeight', window.screen.height / 4, window.screen.height * 2, 1)
-        .onChange(val => updateCameraViewOffset({ fullHeight: val }))
-        .disable();
-    folder1
-        .add(settings, 'offsetX', 0, 256, 1)
-        .onChange(val => updateCameraViewOffset({ x: val }))
-        .disable();
-    folder1
-        .add(settings, 'offsetY', 0, 256, 1)
-        .onChange(val => updateCameraViewOffset({ y: val }))
-        .disable();
-    folder1
-        .add(settings, 'width', window.screen.width / 4, window.screen.width * 2, 1)
-        .onChange(val => updateCameraViewOffset({ width: val }))
-        .disable();
-    folder1
-        .add(settings, 'height', window.screen.height / 4, window.screen.height * 2, 1)
-        .onChange(val => updateCameraViewOffset({ height: val }))
-        .disable();
-    folder1.add(settings, 'clearViewOffset');
-}
-
-function updateCameraViewOffset({ fullWidth, fullHeight, x, y, width, height }) {
-    if (!camera.view) {
-        camera.setViewOffset(
-            fullWidth || window.innerWidth,
-            fullHeight || window.innerHeight,
-            x || 0,
-            y || 0,
-            width || window.innerWidth,
-            height || window.innerHeight,
-        );
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-    } else {
-        camera.setViewOffset(
-            fullWidth || camera.view.fullWidth,
-            fullHeight || camera.view.fullHeight,
-            x || camera.view.offsetX,
-            y || camera.view.offsetY,
-            width || camera.view.width,
-            height || camera.view.height,
-        );
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-    }
-}
diff --git a/examples-testing/examples/css3d_sprites.ts b/examples-testing/examples/css3d_sprites.ts
deleted file mode 100644
index dfe24e79d..000000000
--- a/examples-testing/examples/css3d_sprites.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import * as THREE from 'three';
-
-import TWEEN from 'three/addons/libs/tween.module.js';
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
-
-let camera, scene, renderer;
-let controls;
-
-const particlesTotal = 512;
-const positions = [];
-const objects = [];
-let current = 0;
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.set(600, 400, 1500);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-
-    const image = document.createElement('img');
-    image.addEventListener('load', function () {
-        for (let i = 0; i < particlesTotal; i++) {
-            const object = new CSS3DSprite(image.cloneNode());
-            (object.position.x = Math.random() * 4000 - 2000),
-                (object.position.y = Math.random() * 4000 - 2000),
-                (object.position.z = Math.random() * 4000 - 2000);
-            scene.add(object);
-
-            objects.push(object);
-        }
-
-        transition();
-    });
-    image.src = 'textures/sprite.png';
-
-    // Plane
-
-    const amountX = 16;
-    const amountZ = 32;
-    const separationPlane = 150;
-    const offsetX = ((amountX - 1) * separationPlane) / 2;
-    const offsetZ = ((amountZ - 1) * separationPlane) / 2;
-
-    for (let i = 0; i < particlesTotal; i++) {
-        const x = (i % amountX) * separationPlane;
-        const z = Math.floor(i / amountX) * separationPlane;
-        const y = (Math.sin(x * 0.5) + Math.sin(z * 0.5)) * 200;
-
-        positions.push(x - offsetX, y, z - offsetZ);
-    }
-
-    // Cube
-
-    const amount = 8;
-    const separationCube = 150;
-    const offset = ((amount - 1) * separationCube) / 2;
-
-    for (let i = 0; i < particlesTotal; i++) {
-        const x = (i % amount) * separationCube;
-        const y = Math.floor((i / amount) % amount) * separationCube;
-        const z = Math.floor(i / (amount * amount)) * separationCube;
-
-        positions.push(x - offset, y - offset, z - offset);
-    }
-
-    // Random
-
-    for (let i = 0; i < particlesTotal; i++) {
-        positions.push(Math.random() * 4000 - 2000, Math.random() * 4000 - 2000, Math.random() * 4000 - 2000);
-    }
-
-    // Sphere
-
-    const radius = 750;
-
-    for (let i = 0; i < particlesTotal; i++) {
-        const phi = Math.acos(-1 + (2 * i) / particlesTotal);
-        const theta = Math.sqrt(particlesTotal * Math.PI) * phi;
-
-        positions.push(
-            radius * Math.cos(theta) * Math.sin(phi),
-            radius * Math.sin(theta) * Math.sin(phi),
-            radius * Math.cos(phi),
-        );
-    }
-
-    //
-
-    renderer = new CSS3DRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.getElementById('container').appendChild(renderer.domElement);
-
-    //
-
-    controls = new TrackballControls(camera, renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function transition() {
-    const offset = current * particlesTotal * 3;
-    const duration = 2000;
-
-    for (let i = 0, j = offset; i < particlesTotal; i++, j += 3) {
-        const object = objects[i];
-
-        new TWEEN.Tween(object.position)
-            .to(
-                {
-                    x: positions[j],
-                    y: positions[j + 1],
-                    z: positions[j + 2],
-                },
-                Math.random() * duration + duration,
-            )
-            .easing(TWEEN.Easing.Exponential.InOut)
-            .start();
-    }
-
-    new TWEEN.Tween(this)
-        .to({}, duration * 3)
-        .onComplete(transition)
-        .start();
-
-    current = (current + 1) % 4;
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    TWEEN.update();
-    controls.update();
-
-    const time = performance.now();
-
-    for (let i = 0, l = objects.length; i < l; i++) {
-        const object = objects[i];
-        const scale = Math.sin((Math.floor(object.position.x) + time) * 0.002) * 0.3 + 1;
-        object.scale.set(scale, scale, scale);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/css3d_youtube.ts b/examples-testing/examples/css3d_youtube.ts
deleted file mode 100644
index 62652f87f..000000000
--- a/examples-testing/examples/css3d_youtube.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
-
-let camera, scene, renderer;
-let controls;
-
-function Element(id, x, y, z, ry) {
-    const div = document.createElement('div');
-    div.style.width = '480px';
-    div.style.height = '360px';
-    div.style.backgroundColor = '#000';
-
-    const iframe = document.createElement('iframe');
-    iframe.style.width = '480px';
-    iframe.style.height = '360px';
-    iframe.style.border = '0px';
-    iframe.src = ['https://www.youtube.com/embed/', id, '?rel=0'].join('');
-    div.appendChild(iframe);
-
-    const object = new CSS3DObject(div);
-    object.position.set(x, y, z);
-    object.rotation.y = ry;
-
-    return object;
-}
-
-init();
-animate();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.set(500, 350, 750);
-
-    scene = new THREE.Scene();
-
-    renderer = new CSS3DRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    container.appendChild(renderer.domElement);
-
-    const group = new THREE.Group();
-    group.add(new Element('SJOz3qjfQXU', 0, 0, 240, 0));
-    group.add(new Element('Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2));
-    group.add(new Element('IrydklNpcFI', 0, 0, -240, Math.PI));
-    group.add(new Element('9ubytEsCaS0', -240, 0, 0, -Math.PI / 2));
-    scene.add(group);
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.rotateSpeed = 4;
-
-    window.addEventListener('resize', onWindowResize);
-
-    // Block iframe events when dragging camera
-
-    const blocker = document.getElementById('blocker');
-    blocker.style.display = 'none';
-
-    controls.addEventListener('start', function () {
-        blocker.style.display = '';
-    });
-    controls.addEventListener('end', function () {
-        blocker.style.display = 'none';
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-    controls.update();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/games_fps.ts b/examples-testing/examples/games_fps.ts
deleted file mode 100644
index 4c459f9bc..000000000
--- a/examples-testing/examples/games_fps.ts
+++ /dev/null
@@ -1,372 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { Octree } from 'three/addons/math/Octree.js';
-import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
-
-import { Capsule } from 'three/addons/math/Capsule.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const clock = new THREE.Clock();
-
-const scene = new THREE.Scene();
-scene.background = new THREE.Color(0x88ccee);
-scene.fog = new THREE.Fog(0x88ccee, 0, 50);
-
-const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
-camera.rotation.order = 'YXZ';
-
-const fillLight1 = new THREE.HemisphereLight(0x8dc1de, 0x00668d, 1.5);
-fillLight1.position.set(2, 1, 1);
-scene.add(fillLight1);
-
-const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
-directionalLight.position.set(-5, 25, -1);
-directionalLight.castShadow = true;
-directionalLight.shadow.camera.near = 0.01;
-directionalLight.shadow.camera.far = 500;
-directionalLight.shadow.camera.right = 30;
-directionalLight.shadow.camera.left = -30;
-directionalLight.shadow.camera.top = 30;
-directionalLight.shadow.camera.bottom = -30;
-directionalLight.shadow.mapSize.width = 1024;
-directionalLight.shadow.mapSize.height = 1024;
-directionalLight.shadow.radius = 4;
-directionalLight.shadow.bias = -0.00006;
-scene.add(directionalLight);
-
-const container = document.getElementById('container');
-
-const renderer = new THREE.WebGLRenderer({ antialias: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.setAnimationLoop(animate);
-renderer.shadowMap.enabled = true;
-renderer.shadowMap.type = THREE.VSMShadowMap;
-renderer.toneMapping = THREE.ACESFilmicToneMapping;
-container.appendChild(renderer.domElement);
-
-const stats = new Stats();
-stats.domElement.style.position = 'absolute';
-stats.domElement.style.top = '0px';
-container.appendChild(stats.domElement);
-
-const GRAVITY = 30;
-
-const NUM_SPHERES = 100;
-const SPHERE_RADIUS = 0.2;
-
-const STEPS_PER_FRAME = 5;
-
-const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5);
-const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xdede8d });
-
-const spheres = [];
-let sphereIdx = 0;
-
-for (let i = 0; i < NUM_SPHERES; i++) {
-    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
-    sphere.castShadow = true;
-    sphere.receiveShadow = true;
-
-    scene.add(sphere);
-
-    spheres.push({
-        mesh: sphere,
-        collider: new THREE.Sphere(new THREE.Vector3(0, -100, 0), SPHERE_RADIUS),
-        velocity: new THREE.Vector3(),
-    });
-}
-
-const worldOctree = new Octree();
-
-const playerCollider = new Capsule(new THREE.Vector3(0, 0.35, 0), new THREE.Vector3(0, 1, 0), 0.35);
-
-const playerVelocity = new THREE.Vector3();
-const playerDirection = new THREE.Vector3();
-
-let playerOnFloor = false;
-let mouseTime = 0;
-
-const keyStates = {};
-
-const vector1 = new THREE.Vector3();
-const vector2 = new THREE.Vector3();
-const vector3 = new THREE.Vector3();
-
-document.addEventListener('keydown', event => {
-    keyStates[event.code] = true;
-});
-
-document.addEventListener('keyup', event => {
-    keyStates[event.code] = false;
-});
-
-container.addEventListener('mousedown', () => {
-    document.body.requestPointerLock();
-
-    mouseTime = performance.now();
-});
-
-document.addEventListener('mouseup', () => {
-    if (document.pointerLockElement !== null) throwBall();
-});
-
-document.body.addEventListener('mousemove', event => {
-    if (document.pointerLockElement === document.body) {
-        camera.rotation.y -= event.movementX / 500;
-        camera.rotation.x -= event.movementY / 500;
-    }
-});
-
-window.addEventListener('resize', onWindowResize);
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function throwBall() {
-    const sphere = spheres[sphereIdx];
-
-    camera.getWorldDirection(playerDirection);
-
-    sphere.collider.center.copy(playerCollider.end).addScaledVector(playerDirection, playerCollider.radius * 1.5);
-
-    // throw the ball with more force if we hold the button longer, and if we move forward
-
-    const impulse = 15 + 30 * (1 - Math.exp((mouseTime - performance.now()) * 0.001));
-
-    sphere.velocity.copy(playerDirection).multiplyScalar(impulse);
-    sphere.velocity.addScaledVector(playerVelocity, 2);
-
-    sphereIdx = (sphereIdx + 1) % spheres.length;
-}
-
-function playerCollisions() {
-    const result = worldOctree.capsuleIntersect(playerCollider);
-
-    playerOnFloor = false;
-
-    if (result) {
-        playerOnFloor = result.normal.y > 0;
-
-        if (!playerOnFloor) {
-            playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));
-        }
-
-        if (result.depth >= 1e-10) {
-            playerCollider.translate(result.normal.multiplyScalar(result.depth));
-        }
-    }
-}
-
-function updatePlayer(deltaTime) {
-    let damping = Math.exp(-4 * deltaTime) - 1;
-
-    if (!playerOnFloor) {
-        playerVelocity.y -= GRAVITY * deltaTime;
-
-        // small air resistance
-        damping *= 0.1;
-    }
-
-    playerVelocity.addScaledVector(playerVelocity, damping);
-
-    const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);
-    playerCollider.translate(deltaPosition);
-
-    playerCollisions();
-
-    camera.position.copy(playerCollider.end);
-}
-
-function playerSphereCollision(sphere) {
-    const center = vector1.addVectors(playerCollider.start, playerCollider.end).multiplyScalar(0.5);
-
-    const sphere_center = sphere.collider.center;
-
-    const r = playerCollider.radius + sphere.collider.radius;
-    const r2 = r * r;
-
-    // approximation: player = 3 spheres
-
-    for (const point of [playerCollider.start, playerCollider.end, center]) {
-        const d2 = point.distanceToSquared(sphere_center);
-
-        if (d2 < r2) {
-            const normal = vector1.subVectors(point, sphere_center).normalize();
-            const v1 = vector2.copy(normal).multiplyScalar(normal.dot(playerVelocity));
-            const v2 = vector3.copy(normal).multiplyScalar(normal.dot(sphere.velocity));
-
-            playerVelocity.add(v2).sub(v1);
-            sphere.velocity.add(v1).sub(v2);
-
-            const d = (r - Math.sqrt(d2)) / 2;
-            sphere_center.addScaledVector(normal, -d);
-        }
-    }
-}
-
-function spheresCollisions() {
-    for (let i = 0, length = spheres.length; i < length; i++) {
-        const s1 = spheres[i];
-
-        for (let j = i + 1; j < length; j++) {
-            const s2 = spheres[j];
-
-            const d2 = s1.collider.center.distanceToSquared(s2.collider.center);
-            const r = s1.collider.radius + s2.collider.radius;
-            const r2 = r * r;
-
-            if (d2 < r2) {
-                const normal = vector1.subVectors(s1.collider.center, s2.collider.center).normalize();
-                const v1 = vector2.copy(normal).multiplyScalar(normal.dot(s1.velocity));
-                const v2 = vector3.copy(normal).multiplyScalar(normal.dot(s2.velocity));
-
-                s1.velocity.add(v2).sub(v1);
-                s2.velocity.add(v1).sub(v2);
-
-                const d = (r - Math.sqrt(d2)) / 2;
-
-                s1.collider.center.addScaledVector(normal, d);
-                s2.collider.center.addScaledVector(normal, -d);
-            }
-        }
-    }
-}
-
-function updateSpheres(deltaTime) {
-    spheres.forEach(sphere => {
-        sphere.collider.center.addScaledVector(sphere.velocity, deltaTime);
-
-        const result = worldOctree.sphereIntersect(sphere.collider);
-
-        if (result) {
-            sphere.velocity.addScaledVector(result.normal, -result.normal.dot(sphere.velocity) * 1.5);
-            sphere.collider.center.add(result.normal.multiplyScalar(result.depth));
-        } else {
-            sphere.velocity.y -= GRAVITY * deltaTime;
-        }
-
-        const damping = Math.exp(-1.5 * deltaTime) - 1;
-        sphere.velocity.addScaledVector(sphere.velocity, damping);
-
-        playerSphereCollision(sphere);
-    });
-
-    spheresCollisions();
-
-    for (const sphere of spheres) {
-        sphere.mesh.position.copy(sphere.collider.center);
-    }
-}
-
-function getForwardVector() {
-    camera.getWorldDirection(playerDirection);
-    playerDirection.y = 0;
-    playerDirection.normalize();
-
-    return playerDirection;
-}
-
-function getSideVector() {
-    camera.getWorldDirection(playerDirection);
-    playerDirection.y = 0;
-    playerDirection.normalize();
-    playerDirection.cross(camera.up);
-
-    return playerDirection;
-}
-
-function controls(deltaTime) {
-    // gives a bit of air control
-    const speedDelta = deltaTime * (playerOnFloor ? 25 : 8);
-
-    if (keyStates['KeyW']) {
-        playerVelocity.add(getForwardVector().multiplyScalar(speedDelta));
-    }
-
-    if (keyStates['KeyS']) {
-        playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta));
-    }
-
-    if (keyStates['KeyA']) {
-        playerVelocity.add(getSideVector().multiplyScalar(-speedDelta));
-    }
-
-    if (keyStates['KeyD']) {
-        playerVelocity.add(getSideVector().multiplyScalar(speedDelta));
-    }
-
-    if (playerOnFloor) {
-        if (keyStates['Space']) {
-            playerVelocity.y = 15;
-        }
-    }
-}
-
-const loader = new GLTFLoader().setPath('./models/gltf/');
-
-loader.load('collision-world.glb', gltf => {
-    scene.add(gltf.scene);
-
-    worldOctree.fromGraphNode(gltf.scene);
-
-    gltf.scene.traverse(child => {
-        if (child.isMesh) {
-            child.castShadow = true;
-            child.receiveShadow = true;
-
-            if (child.material.map) {
-                child.material.map.anisotropy = 4;
-            }
-        }
-    });
-
-    const helper = new OctreeHelper(worldOctree);
-    helper.visible = false;
-    scene.add(helper);
-
-    const gui = new GUI({ width: 200 });
-    gui.add({ debug: false }, 'debug').onChange(function (value) {
-        helper.visible = value;
-    });
-});
-
-function teleportPlayerIfOob() {
-    if (camera.position.y <= -25) {
-        playerCollider.start.set(0, 0.35, 0);
-        playerCollider.end.set(0, 1, 0);
-        playerCollider.radius = 0.35;
-        camera.position.copy(playerCollider.end);
-        camera.rotation.set(0, 0, 0);
-    }
-}
-
-function animate() {
-    const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;
-
-    // we look for collisions in substeps to mitigate the risk of
-    // an object traversing another too quickly for detection.
-
-    for (let i = 0; i < STEPS_PER_FRAME; i++) {
-        controls(deltaTime);
-
-        updatePlayer(deltaTime);
-
-        updateSpheres(deltaTime);
-
-        teleportPlayerIfOob();
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/misc_animation_groups.ts b/examples-testing/examples/misc_animation_groups.ts
deleted file mode 100644
index 33fc41997..000000000
--- a/examples-testing/examples/misc_animation_groups.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let stats, clock;
-let scene, camera, renderer, mixer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(50, 50, 100);
-    camera.lookAt(scene.position);
-
-    // all objects of this animation group share a common animation state
-
-    const animationGroup = new THREE.AnimationObjectGroup();
-
-    //
-
-    const geometry = new THREE.BoxGeometry(5, 5, 5);
-    const material = new THREE.MeshBasicMaterial({ transparent: true });
-
-    //
-
-    for (let i = 0; i < 5; i++) {
-        for (let j = 0; j < 5; j++) {
-            const mesh = new THREE.Mesh(geometry, material);
-
-            mesh.position.x = 32 - 16 * i;
-            mesh.position.y = 0;
-            mesh.position.z = 32 - 16 * j;
-
-            scene.add(mesh);
-            animationGroup.add(mesh);
-        }
-    }
-
-    // create some keyframe tracks
-
-    const xAxis = new THREE.Vector3(1, 0, 0);
-    const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
-    const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
-    const quaternionKF = new THREE.QuaternionKeyframeTrack(
-        '.quaternion',
-        [0, 1, 2],
-        [
-            qInitial.x,
-            qInitial.y,
-            qInitial.z,
-            qInitial.w,
-            qFinal.x,
-            qFinal.y,
-            qFinal.z,
-            qFinal.w,
-            qInitial.x,
-            qInitial.y,
-            qInitial.z,
-            qInitial.w,
-        ],
-    );
-
-    const colorKF = new THREE.ColorKeyframeTrack(
-        '.material.color',
-        [0, 1, 2],
-        [1, 0, 0, 0, 1, 0, 0, 0, 1],
-        THREE.InterpolateDiscrete,
-    );
-    const opacityKF = new THREE.NumberKeyframeTrack('.material.opacity', [0, 1, 2], [1, 0, 1]);
-
-    // create clip
-
-    const clip = new THREE.AnimationClip('default', 3, [quaternionKF, colorKF, opacityKF]);
-
-    // apply the animation group to the mixer as the root object
-
-    mixer = new THREE.AnimationMixer(animationGroup);
-
-    const clipAction = mixer.clipAction(clip);
-    clipAction.play();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    clock = new THREE.Clock();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) {
-        mixer.update(delta);
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/misc_animation_keys.ts b/examples-testing/examples/misc_animation_keys.ts
deleted file mode 100644
index e2f141f91..000000000
--- a/examples-testing/examples/misc_animation_keys.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let stats, clock;
-let scene, camera, renderer, mixer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(25, 25, 50);
-    camera.lookAt(scene.position);
-
-    //
-
-    const axesHelper = new THREE.AxesHelper(10);
-    scene.add(axesHelper);
-
-    //
-
-    const geometry = new THREE.BoxGeometry(5, 5, 5);
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true });
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    // create a keyframe track (i.e. a timed sequence of keyframes) for each animated property
-    // Note: the keyframe track type should correspond to the type of the property being animated
-
-    // POSITION
-    const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 30, 0, 0, 0, 0, 0]);
-
-    // SCALE
-    const scaleKF = new THREE.VectorKeyframeTrack('.scale', [0, 1, 2], [1, 1, 1, 2, 2, 2, 1, 1, 1]);
-
-    // ROTATION
-    // Rotation should be performed using quaternions, using a THREE.QuaternionKeyframeTrack
-    // Interpolating Euler angles (.rotation property) can be problematic and is currently not supported
-
-    // set up rotation about x axis
-    const xAxis = new THREE.Vector3(1, 0, 0);
-
-    const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
-    const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
-    const quaternionKF = new THREE.QuaternionKeyframeTrack(
-        '.quaternion',
-        [0, 1, 2],
-        [
-            qInitial.x,
-            qInitial.y,
-            qInitial.z,
-            qInitial.w,
-            qFinal.x,
-            qFinal.y,
-            qFinal.z,
-            qFinal.w,
-            qInitial.x,
-            qInitial.y,
-            qInitial.z,
-            qInitial.w,
-        ],
-    );
-
-    // COLOR
-    const colorKF = new THREE.ColorKeyframeTrack(
-        '.material.color',
-        [0, 1, 2],
-        [1, 0, 0, 0, 1, 0, 0, 0, 1],
-        THREE.InterpolateDiscrete,
-    );
-
-    // OPACITY
-    const opacityKF = new THREE.NumberKeyframeTrack('.material.opacity', [0, 1, 2], [1, 0, 1]);
-
-    // create an animation sequence with the tracks
-    // If a negative time value is passed, the duration will be calculated from the times of the passed tracks array
-    const clip = new THREE.AnimationClip('Action', 3, [scaleKF, positionKF, quaternionKF, colorKF, opacityKF]);
-
-    // setup the THREE.AnimationMixer
-    mixer = new THREE.AnimationMixer(mesh);
-
-    // create a ClipAction and set it to play
-    const clipAction = mixer.clipAction(clip);
-    clipAction.play();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    clock = new THREE.Clock();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) {
-        mixer.update(delta);
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/misc_boxselection.ts b/examples-testing/examples/misc_boxselection.ts
deleted file mode 100644
index e7079c405..000000000
--- a/examples-testing/examples/misc_boxselection.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { SelectionBox } from 'three/addons/interactive/SelectionBox.js';
-import { SelectionHelper } from 'three/addons/interactive/SelectionHelper.js';
-
-let container, stats;
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 500);
-    camera.position.z = 50;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    scene.add(new THREE.AmbientLight(0xaaaaaa));
-
-    const light = new THREE.SpotLight(0xffffff, 10000);
-    light.position.set(0, 25, 50);
-    light.angle = Math.PI / 5;
-
-    light.castShadow = true;
-    light.shadow.camera.near = 10;
-    light.shadow.camera.far = 100;
-    light.shadow.mapSize.width = 1024;
-    light.shadow.mapSize.height = 1024;
-
-    scene.add(light);
-
-    const geometry = new THREE.BoxGeometry();
-
-    for (let i = 0; i < 200; i++) {
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
-
-        object.position.x = Math.random() * 80 - 40;
-        object.position.y = Math.random() * 45 - 25;
-        object.position.z = Math.random() * 45 - 25;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() * 2 + 1;
-        object.scale.y = Math.random() * 2 + 1;
-        object.scale.z = Math.random() * 2 + 1;
-
-        object.castShadow = true;
-        object.receiveShadow = true;
-
-        scene.add(object);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFShadowMap;
-
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
-
-const selectionBox = new SelectionBox(camera, scene);
-const helper = new SelectionHelper(renderer, 'selectBox');
-
-document.addEventListener('pointerdown', function (event) {
-    for (const item of selectionBox.collection) {
-        item.material.emissive.set(0x000000);
-    }
-
-    selectionBox.startPoint.set(
-        (event.clientX / window.innerWidth) * 2 - 1,
-        -(event.clientY / window.innerHeight) * 2 + 1,
-        0.5,
-    );
-});
-
-document.addEventListener('pointermove', function (event) {
-    if (helper.isDown) {
-        for (let i = 0; i < selectionBox.collection.length; i++) {
-            selectionBox.collection[i].material.emissive.set(0x000000);
-        }
-
-        selectionBox.endPoint.set(
-            (event.clientX / window.innerWidth) * 2 - 1,
-            -(event.clientY / window.innerHeight) * 2 + 1,
-            0.5,
-        );
-
-        const allSelected = selectionBox.select();
-
-        for (let i = 0; i < allSelected.length; i++) {
-            allSelected[i].material.emissive.set(0xffffff);
-        }
-    }
-});
-
-document.addEventListener('pointerup', function (event) {
-    selectionBox.endPoint.set(
-        (event.clientX / window.innerWidth) * 2 - 1,
-        -(event.clientY / window.innerHeight) * 2 + 1,
-        0.5,
-    );
-
-    const allSelected = selectionBox.select();
-
-    for (let i = 0; i < allSelected.length; i++) {
-        allSelected[i].material.emissive.set(0xffffff);
-    }
-});
diff --git a/examples-testing/examples/misc_controls_arcball.ts b/examples-testing/examples/misc_controls_arcball.ts
deleted file mode 100644
index fbef33189..000000000
--- a/examples-testing/examples/misc_controls_arcball.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { ArcballControls } from 'three/addons/controls/ArcballControls.js';
-
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-const cameras = ['Orthographic', 'Perspective'];
-const cameraType = { type: 'Perspective' };
-
-const perspectiveDistance = 2.5;
-const orthographicDistance = 120;
-let camera, controls, scene, renderer, gui;
-let folderOptions, folderAnimations;
-
-const arcballGui = {
-    gizmoVisible: true,
-
-    setArcballControls: function () {
-        controls = new ArcballControls(camera, renderer.domElement, scene);
-        controls.addEventListener('change', render);
-
-        this.gizmoVisible = true;
-
-        this.populateGui();
-    },
-
-    populateGui: function () {
-        folderOptions.add(controls, 'enabled').name('Enable controls');
-        folderOptions.add(controls, 'enableGrid').name('Enable Grid');
-        folderOptions.add(controls, 'enableRotate').name('Enable rotate');
-        folderOptions.add(controls, 'enablePan').name('Enable pan');
-        folderOptions.add(controls, 'enableZoom').name('Enable zoom');
-        folderOptions.add(controls, 'cursorZoom').name('Cursor zoom');
-        folderOptions.add(controls, 'adjustNearFar').name('adjust near/far');
-        folderOptions.add(controls, 'scaleFactor', 1.1, 10, 0.1).name('Scale factor');
-        folderOptions.add(controls, 'minDistance', 0, 50, 0.5).name('Min distance');
-        folderOptions.add(controls, 'maxDistance', 0, 50, 0.5).name('Max distance');
-        folderOptions.add(controls, 'minZoom', 0, 50, 0.5).name('Min zoom');
-        folderOptions.add(controls, 'maxZoom', 0, 50, 0.5).name('Max zoom');
-        folderOptions
-            .add(arcballGui, 'gizmoVisible')
-            .name('Show gizmos')
-            .onChange(function () {
-                controls.setGizmosVisible(arcballGui.gizmoVisible);
-            });
-        folderOptions.add(controls, 'copyState').name('Copy state(ctrl+c)');
-        folderOptions.add(controls, 'pasteState').name('Paste state(ctrl+v)');
-        folderOptions.add(controls, 'reset').name('Reset');
-        folderAnimations.add(controls, 'enableAnimations').name('Enable anim.');
-        folderAnimations.add(controls, 'dampingFactor', 0, 100, 1).name('Damping');
-        folderAnimations.add(controls, 'wMax', 0, 100, 1).name('Angular spd');
-    },
-};
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    renderer.toneMappingExposure = 3;
-    renderer.domElement.style.background = 'linear-gradient( 180deg, rgba( 0,0,0,1 ) 0%, rgba( 128,128,255,1 ) 100% )';
-    container.appendChild(renderer.domElement);
-
-    //
-
-    scene = new THREE.Scene();
-
-    camera = makePerspectiveCamera();
-    camera.position.set(0, 0, perspectiveDistance);
-
-    const material = new THREE.MeshStandardMaterial();
-
-    new OBJLoader().setPath('models/obj/cerberus/').load('Cerberus.obj', function (group) {
-        const textureLoader = new THREE.TextureLoader().setPath('models/obj/cerberus/');
-
-        material.roughness = 1;
-        material.metalness = 1;
-
-        const diffuseMap = textureLoader.load('Cerberus_A.jpg', render);
-        diffuseMap.colorSpace = THREE.SRGBColorSpace;
-        material.map = diffuseMap;
-
-        material.metalnessMap = material.roughnessMap = textureLoader.load('Cerberus_RM.jpg', render);
-        material.normalMap = textureLoader.load('Cerberus_N.jpg', render);
-
-        material.map.wrapS = THREE.RepeatWrapping;
-        material.roughnessMap.wrapS = THREE.RepeatWrapping;
-        material.metalnessMap.wrapS = THREE.RepeatWrapping;
-        material.normalMap.wrapS = THREE.RepeatWrapping;
-
-        group.traverse(function (child) {
-            if (child.isMesh) {
-                child.material = material;
-            }
-        });
-
-        group.rotation.y = Math.PI / 2;
-        group.position.x += 0.25;
-        scene.add(group);
-        render();
-
-        new RGBELoader().setPath('textures/equirectangular/').load('venice_sunset_1k.hdr', function (hdrEquirect) {
-            hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
-
-            scene.environment = hdrEquirect;
-
-            render();
-        });
-
-        window.addEventListener('keydown', onKeyDown);
-        window.addEventListener('resize', onWindowResize);
-
-        //
-
-        gui = new GUI();
-        gui.add(cameraType, 'type', cameras)
-            .name('Choose Camera')
-            .onChange(function () {
-                setCamera(cameraType.type);
-            });
-
-        folderOptions = gui.addFolder('Arcball parameters');
-        folderAnimations = folderOptions.addFolder('Animations');
-
-        arcballGui.setArcballControls();
-
-        render();
-    });
-}
-
-function makeOrthographicCamera() {
-    const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
-    const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
-
-    const halfW = perspectiveDistance * Math.tan(halfFovH);
-    const halfH = perspectiveDistance * Math.tan(halfFovV);
-    const near = 0.01;
-    const far = 2000;
-    const newCamera = new THREE.OrthographicCamera(-halfW, halfW, halfH, -halfH, near, far);
-    return newCamera;
-}
-
-function makePerspectiveCamera() {
-    const fov = 45;
-    const aspect = window.innerWidth / window.innerHeight;
-    const near = 0.01;
-    const far = 2000;
-    const newCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
-    return newCamera;
-}
-
-function onWindowResize() {
-    if (camera.type == 'OrthographicCamera') {
-        const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
-        const halfFovH = Math.atan((window.innerWidth / window.innerHeight) * Math.tan(halfFovV));
-
-        const halfW = perspectiveDistance * Math.tan(halfFovH);
-        const halfH = perspectiveDistance * Math.tan(halfFovV);
-        camera.left = -halfW;
-        camera.right = halfW;
-        camera.top = halfH;
-        camera.bottom = -halfH;
-    } else if (camera.type == 'PerspectiveCamera') {
-        camera.aspect = window.innerWidth / window.innerHeight;
-    }
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-function onKeyDown(event) {
-    if (event.key === 'c') {
-        if (event.ctrlKey || event.metaKey) {
-            controls.copyState();
-        }
-    } else if (event.key === 'v') {
-        if (event.ctrlKey || event.metaKey) {
-            controls.pasteState();
-        }
-    }
-}
-
-function setCamera(type) {
-    if (type == 'Orthographic') {
-        camera = makeOrthographicCamera();
-        camera.position.set(0, 0, orthographicDistance);
-    } else if (type == 'Perspective') {
-        camera = makePerspectiveCamera();
-        camera.position.set(0, 0, perspectiveDistance);
-    }
-
-    controls.setCamera(camera);
-
-    render();
-}
diff --git a/examples-testing/examples/misc_controls_drag.ts b/examples-testing/examples/misc_controls_drag.ts
deleted file mode 100644
index b12b0421e..000000000
--- a/examples-testing/examples/misc_controls_drag.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import * as THREE from 'three';
-
-import { DragControls } from 'three/addons/controls/DragControls.js';
-
-let container;
-let camera, scene, renderer;
-let controls, group;
-let enableSelection = false;
-
-const objects = [];
-
-const mouse = new THREE.Vector2(),
-    raycaster = new THREE.Raycaster();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 500);
-    camera.position.z = 25;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    scene.add(new THREE.AmbientLight(0xaaaaaa));
-
-    const light = new THREE.SpotLight(0xffffff, 10000);
-    light.position.set(0, 25, 50);
-    light.angle = Math.PI / 9;
-
-    light.castShadow = true;
-    light.shadow.camera.near = 10;
-    light.shadow.camera.far = 100;
-    light.shadow.mapSize.width = 1024;
-    light.shadow.mapSize.height = 1024;
-
-    scene.add(light);
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const geometry = new THREE.BoxGeometry();
-
-    for (let i = 0; i < 200; i++) {
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
-
-        object.position.x = Math.random() * 30 - 15;
-        object.position.y = Math.random() * 15 - 7.5;
-        object.position.z = Math.random() * 20 - 10;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() * 2 + 1;
-        object.scale.y = Math.random() * 2 + 1;
-        object.scale.z = Math.random() * 2 + 1;
-
-        object.castShadow = true;
-        object.receiveShadow = true;
-
-        scene.add(object);
-
-        objects.push(object);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFShadowMap;
-
-    container.appendChild(renderer.domElement);
-
-    controls = new DragControls([...objects], camera, renderer.domElement);
-    controls.rotateSpeed = 2;
-    controls.addEventListener('drag', render);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    document.addEventListener('click', onClick);
-    window.addEventListener('keydown', onKeyDown);
-    window.addEventListener('keyup', onKeyUp);
-
-    render();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function onKeyDown(event) {
-    enableSelection = event.keyCode === 16 ? true : false;
-
-    if (event.keyCode === 77) {
-        controls.touches.ONE = controls.touches.ONE === THREE.TOUCH.PAN ? THREE.TOUCH.ROTATE : THREE.TOUCH.PAN;
-    }
-}
-
-function onKeyUp() {
-    enableSelection = false;
-}
-
-function onClick(event) {
-    event.preventDefault();
-
-    if (enableSelection === true) {
-        const draggableObjects = controls.objects;
-        draggableObjects.length = 0;
-
-        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-        raycaster.setFromCamera(mouse, camera);
-
-        const intersections = raycaster.intersectObjects(objects, true);
-
-        if (intersections.length > 0) {
-            const object = intersections[0].object;
-
-            if (group.children.includes(object) === true) {
-                object.material.emissive.set(0x000000);
-                scene.attach(object);
-            } else {
-                object.material.emissive.set(0xaaaaaa);
-                group.attach(object);
-            }
-
-            controls.transformGroup = true;
-            draggableObjects.push(group);
-        }
-
-        if (group.children.length === 0) {
-            controls.transformGroup = false;
-            draggableObjects.push(...objects);
-        }
-    }
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_controls_fly.ts b/examples-testing/examples/misc_controls_fly.ts
deleted file mode 100644
index 5b25c4895..000000000
--- a/examples-testing/examples/misc_controls_fly.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import * as THREE from 'three';
-import { pass, film } from 'three/tsl';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { FlyControls } from 'three/addons/controls/FlyControls.js';
-
-const radius = 6371;
-const tilt = 0.41;
-const rotationSpeed = 0.02;
-
-const cloudsScale = 1.005;
-const moonScale = 0.23;
-
-const MARGIN = 0;
-let SCREEN_HEIGHT = window.innerHeight - MARGIN * 2;
-let SCREEN_WIDTH = window.innerWidth;
-
-let camera, controls, scene, renderer, stats;
-let geometry, meshPlanet, meshClouds, meshMoon;
-let dirLight;
-
-let postProcessing;
-
-const textureLoader = new THREE.TextureLoader();
-
-let d, dPlanet, dMoon;
-const dMoonVec = new THREE.Vector3();
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(25, SCREEN_WIDTH / SCREEN_HEIGHT, 50, 1e7);
-    camera.position.z = radius * 5;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.FogExp2(0x000000, 0.00000025);
-
-    dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(-1, 0, 1).normalize();
-    scene.add(dirLight);
-
-    const materialNormalMap = new THREE.MeshPhongMaterial({
-        specular: 0x7c7c7c,
-        shininess: 15,
-        map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),
-        specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),
-        normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),
-
-        // y scale is negated to compensate for normal map handedness.
-        normalScale: new THREE.Vector2(0.85, -0.85),
-    });
-    materialNormalMap.map.colorSpace = THREE.SRGBColorSpace;
-
-    // planet
-
-    geometry = new THREE.SphereGeometry(radius, 100, 50);
-
-    meshPlanet = new THREE.Mesh(geometry, materialNormalMap);
-    meshPlanet.rotation.y = 0;
-    meshPlanet.rotation.z = tilt;
-    scene.add(meshPlanet);
-
-    // clouds
-
-    const materialClouds = new THREE.MeshLambertMaterial({
-        map: textureLoader.load('textures/planets/earth_clouds_1024.png'),
-        transparent: true,
-    });
-    materialClouds.map.colorSpace = THREE.SRGBColorSpace;
-
-    meshClouds = new THREE.Mesh(geometry, materialClouds);
-    meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale);
-    meshClouds.rotation.z = tilt;
-    scene.add(meshClouds);
-
-    // moon
-
-    const materialMoon = new THREE.MeshPhongMaterial({
-        map: textureLoader.load('textures/planets/moon_1024.jpg'),
-    });
-    materialMoon.map.colorSpace = THREE.SRGBColorSpace;
-
-    meshMoon = new THREE.Mesh(geometry, materialMoon);
-    meshMoon.position.set(radius * 5, 0, 0);
-    meshMoon.scale.set(moonScale, moonScale, moonScale);
-    scene.add(meshMoon);
-
-    // stars
-
-    const r = radius,
-        starsGeometry = [new THREE.BufferGeometry(), new THREE.BufferGeometry()];
-
-    const vertices1 = [];
-    const vertices2 = [];
-
-    const vertex = new THREE.Vector3();
-
-    for (let i = 0; i < 250; i++) {
-        vertex.x = Math.random() * 2 - 1;
-        vertex.y = Math.random() * 2 - 1;
-        vertex.z = Math.random() * 2 - 1;
-        vertex.multiplyScalar(r);
-
-        vertices1.push(vertex.x, vertex.y, vertex.z);
-    }
-
-    for (let i = 0; i < 1500; i++) {
-        vertex.x = Math.random() * 2 - 1;
-        vertex.y = Math.random() * 2 - 1;
-        vertex.z = Math.random() * 2 - 1;
-        vertex.multiplyScalar(r);
-
-        vertices2.push(vertex.x, vertex.y, vertex.z);
-    }
-
-    starsGeometry[0].setAttribute('position', new THREE.Float32BufferAttribute(vertices1, 3));
-    starsGeometry[1].setAttribute('position', new THREE.Float32BufferAttribute(vertices2, 3));
-
-    const starsMaterials = [
-        new THREE.PointsMaterial({ color: 0x9c9c9c }),
-        new THREE.PointsMaterial({ color: 0x838383 }),
-        new THREE.PointsMaterial({ color: 0x5a5a5a }),
-    ];
-
-    for (let i = 10; i < 30; i++) {
-        const stars = new THREE.Points(starsGeometry[i % 2], starsMaterials[i % 3]);
-
-        stars.rotation.x = Math.random() * 6;
-        stars.rotation.y = Math.random() * 6;
-        stars.rotation.z = Math.random() * 6;
-        stars.scale.setScalar(i * 10);
-
-        stars.matrixAutoUpdate = false;
-        stars.updateMatrix();
-
-        scene.add(stars);
-    }
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    controls = new FlyControls(camera, renderer.domElement);
-
-    controls.movementSpeed = 1000;
-    controls.domElement = renderer.domElement;
-    controls.rollSpeed = Math.PI / 24;
-    controls.autoForward = false;
-    controls.dragToLook = false;
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // postprocessing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    const scenePassColor = scenePass.getTextureNode();
-
-    postProcessing.outputNode = film(scenePassColor);
-}
-
-function onWindowResize() {
-    SCREEN_HEIGHT = window.innerHeight;
-    SCREEN_WIDTH = window.innerWidth;
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    // rotate the planet and clouds
-
-    const delta = clock.getDelta();
-
-    meshPlanet.rotation.y += rotationSpeed * delta;
-    meshClouds.rotation.y += 1.25 * rotationSpeed * delta;
-
-    // slow down as we approach the surface
-
-    dPlanet = camera.position.length();
-
-    dMoonVec.subVectors(camera.position, meshMoon.position);
-    dMoon = dMoonVec.length();
-
-    if (dMoon < dPlanet) {
-        d = dMoon - radius * moonScale * 1.01;
-    } else {
-        d = dPlanet - radius * 1.01;
-    }
-
-    controls.movementSpeed = 0.33 * d;
-    controls.update(delta);
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/misc_controls_map.ts b/examples-testing/examples/misc_controls_map.ts
deleted file mode 100644
index 2f52190cf..000000000
--- a/examples-testing/examples/misc_controls_map.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { MapControls } from 'three/addons/controls/MapControls.js';
-
-let camera, controls, scene, renderer;
-
-init();
-//render(); // remove when using animation loop
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xcccccc);
-    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 200, -400);
-
-    // controls
-
-    controls = new MapControls(camera, renderer.domElement);
-
-    //controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
-
-    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
-    controls.dampingFactor = 0.05;
-
-    controls.screenSpacePanning = false;
-
-    controls.minDistance = 100;
-    controls.maxDistance = 500;
-
-    controls.maxPolarAngle = Math.PI / 2;
-
-    // world
-
-    const geometry = new THREE.BoxGeometry();
-    geometry.translate(0, 0.5, 0);
-    const material = new THREE.MeshPhongMaterial({ color: 0xeeeeee, flatShading: true });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 1600 - 800;
-        mesh.position.y = 0;
-        mesh.position.z = Math.random() * 1600 - 800;
-        mesh.scale.x = 20;
-        mesh.scale.y = Math.random() * 80 + 10;
-        mesh.scale.z = 20;
-        mesh.updateMatrix();
-        mesh.matrixAutoUpdate = false;
-        scene.add(mesh);
-    }
-
-    // lights
-
-    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight1.position.set(1, 1, 1);
-    scene.add(dirLight1);
-
-    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
-    dirLight2.position.set(-1, -1, -1);
-    scene.add(dirLight2);
-
-    const ambientLight = new THREE.AmbientLight(0x555555);
-    scene.add(ambientLight);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-    gui.add(controls, 'zoomToCursor');
-    gui.add(controls, 'screenSpacePanning');
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_controls_orbit.ts b/examples-testing/examples/misc_controls_orbit.ts
deleted file mode 100644
index 186e216cb..000000000
--- a/examples-testing/examples/misc_controls_orbit.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, controls, scene, renderer;
-
-init();
-//render(); // remove when using animation loop
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xcccccc);
-    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(400, 200, 0);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.listenToKeyEvents(window); // optional
-
-    //controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
-
-    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
-    controls.dampingFactor = 0.05;
-
-    controls.screenSpacePanning = false;
-
-    controls.minDistance = 100;
-    controls.maxDistance = 500;
-
-    controls.maxPolarAngle = Math.PI / 2;
-
-    // world
-
-    const geometry = new THREE.ConeGeometry(10, 30, 4, 1);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 1600 - 800;
-        mesh.position.y = 0;
-        mesh.position.z = Math.random() * 1600 - 800;
-        mesh.updateMatrix();
-        mesh.matrixAutoUpdate = false;
-        scene.add(mesh);
-    }
-
-    // lights
-
-    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight1.position.set(1, 1, 1);
-    scene.add(dirLight1);
-
-    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
-    dirLight2.position.set(-1, -1, -1);
-    scene.add(dirLight2);
-
-    const ambientLight = new THREE.AmbientLight(0x555555);
-    scene.add(ambientLight);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_controls_pointerlock.ts b/examples-testing/examples/misc_controls_pointerlock.ts
deleted file mode 100644
index 0b6fcc516..000000000
--- a/examples-testing/examples/misc_controls_pointerlock.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import * as THREE from 'three';
-
-import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
-
-let camera, scene, renderer, controls;
-
-const objects = [];
-
-let raycaster;
-
-let moveForward = false;
-let moveBackward = false;
-let moveLeft = false;
-let moveRight = false;
-let canJump = false;
-
-let prevTime = performance.now();
-const velocity = new THREE.Vector3();
-const direction = new THREE.Vector3();
-const vertex = new THREE.Vector3();
-const color = new THREE.Color();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.y = 10;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-    scene.fog = new THREE.Fog(0xffffff, 0, 750);
-
-    const light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 2.5);
-    light.position.set(0.5, 1, 0.75);
-    scene.add(light);
-
-    controls = new PointerLockControls(camera, document.body);
-
-    const blocker = document.getElementById('blocker');
-    const instructions = document.getElementById('instructions');
-
-    instructions.addEventListener('click', function () {
-        controls.lock();
-    });
-
-    controls.addEventListener('lock', function () {
-        instructions.style.display = 'none';
-        blocker.style.display = 'none';
-    });
-
-    controls.addEventListener('unlock', function () {
-        blocker.style.display = 'block';
-        instructions.style.display = '';
-    });
-
-    scene.add(controls.object);
-
-    const onKeyDown = function (event) {
-        switch (event.code) {
-            case 'ArrowUp':
-            case 'KeyW':
-                moveForward = true;
-                break;
-
-            case 'ArrowLeft':
-            case 'KeyA':
-                moveLeft = true;
-                break;
-
-            case 'ArrowDown':
-            case 'KeyS':
-                moveBackward = true;
-                break;
-
-            case 'ArrowRight':
-            case 'KeyD':
-                moveRight = true;
-                break;
-
-            case 'Space':
-                if (canJump === true) velocity.y += 350;
-                canJump = false;
-                break;
-        }
-    };
-
-    const onKeyUp = function (event) {
-        switch (event.code) {
-            case 'ArrowUp':
-            case 'KeyW':
-                moveForward = false;
-                break;
-
-            case 'ArrowLeft':
-            case 'KeyA':
-                moveLeft = false;
-                break;
-
-            case 'ArrowDown':
-            case 'KeyS':
-                moveBackward = false;
-                break;
-
-            case 'ArrowRight':
-            case 'KeyD':
-                moveRight = false;
-                break;
-        }
-    };
-
-    document.addEventListener('keydown', onKeyDown);
-    document.addEventListener('keyup', onKeyUp);
-
-    raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 10);
-
-    // floor
-
-    let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100);
-    floorGeometry.rotateX(-Math.PI / 2);
-
-    // vertex displacement
-
-    let position = floorGeometry.attributes.position;
-
-    for (let i = 0, l = position.count; i < l; i++) {
-        vertex.fromBufferAttribute(position, i);
-
-        vertex.x += Math.random() * 20 - 10;
-        vertex.y += Math.random() * 2;
-        vertex.z += Math.random() * 20 - 10;
-
-        position.setXYZ(i, vertex.x, vertex.y, vertex.z);
-    }
-
-    floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
-
-    position = floorGeometry.attributes.position;
-    const colorsFloor = [];
-
-    for (let i = 0, l = position.count; i < l; i++) {
-        color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
-        colorsFloor.push(color.r, color.g, color.b);
-    }
-
-    floorGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsFloor, 3));
-
-    const floorMaterial = new THREE.MeshBasicMaterial({ vertexColors: true });
-
-    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
-    scene.add(floor);
-
-    // objects
-
-    const boxGeometry = new THREE.BoxGeometry(20, 20, 20).toNonIndexed();
-
-    position = boxGeometry.attributes.position;
-    const colorsBox = [];
-
-    for (let i = 0, l = position.count; i < l; i++) {
-        color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
-        colorsBox.push(color.r, color.g, color.b);
-    }
-
-    boxGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsBox, 3));
-
-    for (let i = 0; i < 500; i++) {
-        const boxMaterial = new THREE.MeshPhongMaterial({ specular: 0xffffff, flatShading: true, vertexColors: true });
-        boxMaterial.color.setHSL(Math.random() * 0.2 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace);
-
-        const box = new THREE.Mesh(boxGeometry, boxMaterial);
-        box.position.x = Math.floor(Math.random() * 20 - 10) * 20;
-        box.position.y = Math.floor(Math.random() * 20) * 20 + 10;
-        box.position.z = Math.floor(Math.random() * 20 - 10) * 20;
-
-        scene.add(box);
-        objects.push(box);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = performance.now();
-
-    if (controls.isLocked === true) {
-        raycaster.ray.origin.copy(controls.object.position);
-        raycaster.ray.origin.y -= 10;
-
-        const intersections = raycaster.intersectObjects(objects, false);
-
-        const onObject = intersections.length > 0;
-
-        const delta = (time - prevTime) / 1000;
-
-        velocity.x -= velocity.x * 10.0 * delta;
-        velocity.z -= velocity.z * 10.0 * delta;
-
-        velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
-
-        direction.z = Number(moveForward) - Number(moveBackward);
-        direction.x = Number(moveRight) - Number(moveLeft);
-        direction.normalize(); // this ensures consistent movements in all directions
-
-        if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta;
-        if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta;
-
-        if (onObject === true) {
-            velocity.y = Math.max(0, velocity.y);
-            canJump = true;
-        }
-
-        controls.moveRight(-velocity.x * delta);
-        controls.moveForward(-velocity.z * delta);
-
-        controls.object.position.y += velocity.y * delta; // new behavior
-
-        if (controls.object.position.y < 10) {
-            velocity.y = 0;
-            controls.object.position.y = 10;
-
-            canJump = true;
-        }
-    }
-
-    prevTime = time;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_controls_trackball.ts b/examples-testing/examples/misc_controls_trackball.ts
deleted file mode 100644
index b6479e9f6..000000000
--- a/examples-testing/examples/misc_controls_trackball.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-
-let perspectiveCamera, orthographicCamera, controls, scene, renderer, stats;
-
-const params = {
-    orthographicCamera: false,
-};
-
-const frustumSize = 400;
-
-init();
-
-function init() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    perspectiveCamera = new THREE.PerspectiveCamera(60, aspect, 1, 1000);
-    perspectiveCamera.position.z = 500;
-
-    orthographicCamera = new THREE.OrthographicCamera(
-        (frustumSize * aspect) / -2,
-        (frustumSize * aspect) / 2,
-        frustumSize / 2,
-        frustumSize / -2,
-        1,
-        1000,
-    );
-    orthographicCamera.position.z = 500;
-
-    // world
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xcccccc);
-    scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
-
-    const geometry = new THREE.ConeGeometry(10, 30, 4, 1);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = (Math.random() - 0.5) * 1000;
-        mesh.position.y = (Math.random() - 0.5) * 1000;
-        mesh.position.z = (Math.random() - 0.5) * 1000;
-        mesh.updateMatrix();
-        mesh.matrixAutoUpdate = false;
-        scene.add(mesh);
-    }
-
-    // lights
-
-    const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight1.position.set(1, 1, 1);
-    scene.add(dirLight1);
-
-    const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
-    dirLight2.position.set(-1, -1, -1);
-    scene.add(dirLight2);
-
-    const ambientLight = new THREE.AmbientLight(0x555555);
-    scene.add(ambientLight);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'orthographicCamera')
-        .name('use orthographic')
-        .onChange(function (value) {
-            controls.dispose();
-
-            createControls(value ? orthographicCamera : perspectiveCamera);
-        });
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    createControls(perspectiveCamera);
-}
-
-function createControls(camera) {
-    controls = new TrackballControls(camera, renderer.domElement);
-
-    controls.rotateSpeed = 1.0;
-    controls.zoomSpeed = 1.2;
-    controls.panSpeed = 0.8;
-
-    controls.keys = ['KeyA', 'KeyS', 'KeyD'];
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    perspectiveCamera.aspect = aspect;
-    perspectiveCamera.updateProjectionMatrix();
-
-    orthographicCamera.left = (-frustumSize * aspect) / 2;
-    orthographicCamera.right = (frustumSize * aspect) / 2;
-    orthographicCamera.top = frustumSize / 2;
-    orthographicCamera.bottom = -frustumSize / 2;
-    orthographicCamera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-function animate() {
-    controls.update();
-
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const camera = params.orthographicCamera ? orthographicCamera : perspectiveCamera;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_controls_transform.ts b/examples-testing/examples/misc_controls_transform.ts
deleted file mode 100644
index 9d14bf7ee..000000000
--- a/examples-testing/examples/misc_controls_transform.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { TransformControls } from 'three/addons/controls/TransformControls.js';
-
-let cameraPersp, cameraOrtho, currentCamera;
-let scene, renderer, control, orbit;
-
-init();
-render();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const aspect = window.innerWidth / window.innerHeight;
-
-    const frustumSize = 5;
-
-    cameraPersp = new THREE.PerspectiveCamera(50, aspect, 0.1, 100);
-    cameraOrtho = new THREE.OrthographicCamera(
-        -frustumSize * aspect,
-        frustumSize * aspect,
-        frustumSize,
-        -frustumSize,
-        0.1,
-        100,
-    );
-    currentCamera = cameraPersp;
-
-    currentCamera.position.set(5, 2.5, 5);
-
-    scene = new THREE.Scene();
-    scene.add(new THREE.GridHelper(5, 10, 0x888888, 0x444444));
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const light = new THREE.DirectionalLight(0xffffff, 4);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    const texture = new THREE.TextureLoader().load('textures/crate.gif', render);
-    texture.colorSpace = THREE.SRGBColorSpace;
-    texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
-
-    const geometry = new THREE.BoxGeometry();
-    const material = new THREE.MeshLambertMaterial({ map: texture });
-
-    orbit = new OrbitControls(currentCamera, renderer.domElement);
-    orbit.update();
-    orbit.addEventListener('change', render);
-
-    control = new TransformControls(currentCamera, renderer.domElement);
-    control.addEventListener('change', render);
-
-    control.addEventListener('dragging-changed', function (event) {
-        orbit.enabled = !event.value;
-    });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    control.attach(mesh);
-    scene.add(control);
-
-    window.addEventListener('resize', onWindowResize);
-
-    window.addEventListener('keydown', function (event) {
-        switch (event.key) {
-            case 'q':
-                control.setSpace(control.space === 'local' ? 'world' : 'local');
-                break;
-
-            case 'Shift':
-                control.setTranslationSnap(1);
-                control.setRotationSnap(THREE.MathUtils.degToRad(15));
-                control.setScaleSnap(0.25);
-                break;
-
-            case 'w':
-                control.setMode('translate');
-                break;
-
-            case 'e':
-                control.setMode('rotate');
-                break;
-
-            case 'r':
-                control.setMode('scale');
-                break;
-
-            case 'c':
-                const position = currentCamera.position.clone();
-
-                currentCamera = currentCamera.isPerspectiveCamera ? cameraOrtho : cameraPersp;
-                currentCamera.position.copy(position);
-
-                orbit.object = currentCamera;
-                control.camera = currentCamera;
-
-                currentCamera.lookAt(orbit.target.x, orbit.target.y, orbit.target.z);
-                onWindowResize();
-                break;
-
-            case 'v':
-                const randomFoV = Math.random() + 0.1;
-                const randomZoom = Math.random() + 0.1;
-
-                cameraPersp.fov = randomFoV * 160;
-                cameraOrtho.bottom = -randomFoV * 500;
-                cameraOrtho.top = randomFoV * 500;
-
-                cameraPersp.zoom = randomZoom * 5;
-                cameraOrtho.zoom = randomZoom * 5;
-                onWindowResize();
-                break;
-
-            case '+':
-            case '=':
-                control.setSize(control.size + 0.1);
-                break;
-
-            case '-':
-            case '_':
-                control.setSize(Math.max(control.size - 0.1, 0.1));
-                break;
-
-            case 'x':
-                control.showX = !control.showX;
-                break;
-
-            case 'y':
-                control.showY = !control.showY;
-                break;
-
-            case 'z':
-                control.showZ = !control.showZ;
-                break;
-
-            case ' ':
-                control.enabled = !control.enabled;
-                break;
-
-            case 'Escape':
-                control.reset();
-                break;
-        }
-    });
-
-    window.addEventListener('keyup', function (event) {
-        switch (event.key) {
-            case 'Shift':
-                control.setTranslationSnap(null);
-                control.setRotationSnap(null);
-                control.setScaleSnap(null);
-                break;
-        }
-    });
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    cameraPersp.aspect = aspect;
-    cameraPersp.updateProjectionMatrix();
-
-    cameraOrtho.left = cameraOrtho.bottom * aspect;
-    cameraOrtho.right = cameraOrtho.top * aspect;
-    cameraOrtho.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, currentCamera);
-}
diff --git a/examples-testing/examples/misc_exporter_draco.ts b/examples-testing/examples/misc_exporter_draco.ts
deleted file mode 100644
index 40a62fb18..000000000
--- a/examples-testing/examples/misc_exporter_draco.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, renderer, exporter, mesh;
-
-const params = {
-    export: exportFile,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(4, 2, 4);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
-
-    exporter = new DRACOExporter();
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(0, 20, 10);
-    directionalLight.castShadow = true;
-    directionalLight.shadow.camera.top = 2;
-    directionalLight.shadow.camera.bottom = -2;
-    directionalLight.shadow.camera.left = -2;
-    directionalLight.shadow.camera.right = 2;
-    scene.add(directionalLight);
-
-    // ground
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(40, 40),
-        new THREE.MeshPhongMaterial({ color: 0xbbbbbb, depthWrite: false }),
-    );
-    ground.rotation.x = -Math.PI / 2;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
-    grid.material.opacity = 0.2;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    // export mesh
-
-    const geometry = new THREE.TorusKnotGeometry(0.75, 0.2, 200, 30);
-    const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.castShadow = true;
-    mesh.position.y = 1.5;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 1.5, 0);
-    controls.update();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'export').name('Export DRC');
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
-
-function exportFile() {
-    const result = exporter.parse(mesh);
-    saveArrayBuffer(result, 'file.drc');
-}
-
-const link = document.createElement('a');
-link.style.display = 'none';
-document.body.appendChild(link);
-
-function save(blob, filename) {
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-}
-
-function saveArrayBuffer(buffer, filename) {
-    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
-}
diff --git a/examples-testing/examples/misc_exporter_exr.ts b/examples-testing/examples/misc_exporter_exr.ts
deleted file mode 100644
index c239a65fa..000000000
--- a/examples-testing/examples/misc_exporter_exr.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { EXRExporter, ZIP_COMPRESSION, ZIPS_COMPRESSION, NO_COMPRESSION } from 'three/addons/exporters/EXRExporter.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, renderer, exporter, mesh, controls, renderTarget, dataTexture;
-
-const params = {
-    target: 'pmrem',
-    type: 'HalfFloatType',
-    compression: 'ZIP',
-    export: exportFile,
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(10, 0, 0);
-
-    scene = new THREE.Scene();
-
-    exporter = new EXRExporter();
-    const rgbeloader = new RGBELoader();
-
-    //
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    pmremGenerator.compileEquirectangularShader();
-
-    rgbeloader.load('textures/equirectangular/san_giuseppe_bridge_2k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        renderTarget = pmremGenerator.fromEquirectangular(texture);
-        scene.background = renderTarget.texture;
-    });
-
-    createDataTexture();
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.rotateSpeed = -0.25; // negative, to track mouse pointer
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    const input = gui.addFolder('Input');
-    input.add(params, 'target').options(['pmrem', 'data-texture']).onChange(swapScene);
-
-    const options = gui.addFolder('Output Options');
-    options.add(params, 'type').options(['FloatType', 'HalfFloatType']);
-    options.add(params, 'compression').options(['ZIP', 'ZIPS', 'NONE']);
-
-    gui.add(params, 'export').name('Export EXR');
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-    renderer.render(scene, camera);
-}
-
-function createDataTexture() {
-    const normal = new THREE.Vector3();
-    const coord = new THREE.Vector2();
-    const size = 800,
-        radius = 320,
-        factor = (Math.PI * 0.5) / radius;
-    const data = new Float32Array(4 * size * size);
-
-    for (let i = 0; i < size; i++) {
-        for (let j = 0; j < size; j++) {
-            const idx = i * size * 4 + j * 4;
-            coord.set(j, i).subScalar(size / 2);
-
-            if (coord.length() < radius)
-                normal.set(Math.sin(coord.x * factor), Math.sin(coord.y * factor), Math.cos(coord.x * factor));
-            else normal.set(0, 0, 1);
-
-            data[idx + 0] = 0.5 + 0.5 * normal.x;
-            data[idx + 1] = 0.5 + 0.5 * normal.y;
-            data[idx + 2] = 0.5 + 0.5 * normal.z;
-            data[idx + 3] = 1;
-        }
-    }
-
-    dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);
-    dataTexture.needsUpdate = true;
-
-    const material = new THREE.MeshBasicMaterial({ map: dataTexture });
-    const quad = new THREE.PlaneGeometry(50, 50);
-    mesh = new THREE.Mesh(quad, material);
-    mesh.visible = false;
-
-    scene.add(mesh);
-}
-
-function swapScene() {
-    if (params.target == 'pmrem') {
-        camera.position.set(10, 0, 0);
-        controls.enabled = true;
-        scene.background = renderTarget.texture;
-        mesh.visible = false;
-    } else {
-        camera.position.set(0, 0, 70);
-        controls.enabled = false;
-        scene.background = new THREE.Color(0, 0, 0);
-        mesh.visible = true;
-    }
-}
-
-function exportFile() {
-    let result, exportType, exportCompression;
-
-    if (params.type == 'HalfFloatType') exportType = THREE.HalfFloatType;
-    else exportType = THREE.FloatType;
-
-    if (params.compression == 'ZIP') exportCompression = ZIP_COMPRESSION;
-    else if (params.compression == 'ZIPS') exportCompression = ZIPS_COMPRESSION;
-    else exportCompression = NO_COMPRESSION;
-
-    if (params.target == 'pmrem')
-        result = exporter.parse(renderer, renderTarget, { type: exportType, compression: exportCompression });
-    else result = exporter.parse(dataTexture, { type: exportType, compression: exportCompression });
-
-    saveArrayBuffer(result, params.target + '.exr');
-}
-
-function saveArrayBuffer(buffer, filename) {
-    const blob = new Blob([buffer], { type: 'image/x-exr' });
-    const link = document.createElement('a');
-
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-}
diff --git a/examples-testing/examples/misc_exporter_gltf.ts b/examples-testing/examples/misc_exporter_gltf.ts
deleted file mode 100644
index e4172b852..000000000
--- a/examples-testing/examples/misc_exporter_gltf.ts
+++ /dev/null
@@ -1,507 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-function exportGLTF(input) {
-    const gltfExporter = new GLTFExporter();
-
-    const options = {
-        trs: params.trs,
-        onlyVisible: params.onlyVisible,
-        binary: params.binary,
-        maxTextureSize: params.maxTextureSize,
-    };
-    gltfExporter.parse(
-        input,
-        function (result) {
-            if (result instanceof ArrayBuffer) {
-                saveArrayBuffer(result, 'scene.glb');
-            } else {
-                const output = JSON.stringify(result, null, 2);
-                console.log(output);
-                saveString(output, 'scene.gltf');
-            }
-        },
-        function (error) {
-            console.log('An error happened during parsing', error);
-        },
-        options,
-    );
-}
-
-const link = document.createElement('a');
-link.style.display = 'none';
-document.body.appendChild(link); // Firefox workaround, see #6594
-
-function save(blob, filename) {
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-
-    // URL.revokeObjectURL( url ); breaks Firefox...
-}
-
-function saveString(text, filename) {
-    save(new Blob([text], { type: 'text/plain' }), filename);
-}
-
-function saveArrayBuffer(buffer, filename) {
-    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
-}
-
-let container;
-
-let camera, object, object2, material, geometry, scene1, scene2, renderer;
-let gridHelper, sphere, model, coffeemat;
-
-const params = {
-    trs: false,
-    onlyVisible: true,
-    binary: false,
-    maxTextureSize: 4096,
-    exportScene1: exportScene1,
-    exportScenes: exportScenes,
-    exportSphere: exportSphere,
-    exportModel: exportModel,
-    exportObjects: exportObjects,
-    exportSceneObject: exportSceneObject,
-    exportCompressedObject: exportCompressedObject,
-};
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // Make linear gradient texture
-
-    const data = new Uint8ClampedArray(100 * 100 * 4);
-
-    for (let y = 0; y < 100; y++) {
-        for (let x = 0; x < 100; x++) {
-            const stride = 4 * (100 * y + x);
-
-            data[stride] = Math.round((255 * y) / 99);
-            data[stride + 1] = Math.round(255 - (255 * y) / 99);
-            data[stride + 2] = 0;
-            data[stride + 3] = 255;
-        }
-    }
-
-    const gradientTexture = new THREE.DataTexture(data, 100, 100, THREE.RGBAFormat);
-    gradientTexture.minFilter = THREE.LinearFilter;
-    gradientTexture.magFilter = THREE.LinearFilter;
-    gradientTexture.needsUpdate = true;
-
-    scene1 = new THREE.Scene();
-    scene1.name = 'Scene1';
-
-    // ---------------------------------------------------------------------
-    // Perspective Camera
-    // ---------------------------------------------------------------------
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(600, 400, 0);
-
-    camera.name = 'PerspectiveCamera';
-    scene1.add(camera);
-
-    // ---------------------------------------------------------------------
-    // Ambient light
-    // ---------------------------------------------------------------------
-    const ambientLight = new THREE.AmbientLight(0xcccccc);
-    ambientLight.name = 'AmbientLight';
-    scene1.add(ambientLight);
-
-    // ---------------------------------------------------------------------
-    // DirectLight
-    // ---------------------------------------------------------------------
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.target.position.set(0, 0, -1);
-    dirLight.add(dirLight.target);
-    dirLight.lookAt(-1, -1, 0);
-    dirLight.name = 'DirectionalLight';
-    scene1.add(dirLight);
-
-    // ---------------------------------------------------------------------
-    // Grid
-    // ---------------------------------------------------------------------
-    gridHelper = new THREE.GridHelper(2000, 20, 0xc1c1c1, 0x8d8d8d);
-    gridHelper.position.y = -50;
-    gridHelper.name = 'Grid';
-    scene1.add(gridHelper);
-
-    // ---------------------------------------------------------------------
-    // Axes
-    // ---------------------------------------------------------------------
-    const axes = new THREE.AxesHelper(500);
-    axes.name = 'AxesHelper';
-    scene1.add(axes);
-
-    // ---------------------------------------------------------------------
-    // Simple geometry with basic material
-    // ---------------------------------------------------------------------
-    // Icosahedron
-    const mapGrid = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    mapGrid.wrapS = mapGrid.wrapT = THREE.RepeatWrapping;
-    mapGrid.colorSpace = THREE.SRGBColorSpace;
-    material = new THREE.MeshBasicMaterial({
-        color: 0xffffff,
-        map: mapGrid,
-    });
-
-    object = new THREE.Mesh(new THREE.IcosahedronGeometry(75, 0), material);
-    object.position.set(-200, 0, 200);
-    object.name = 'Icosahedron';
-    scene1.add(object);
-
-    // Octahedron
-    material = new THREE.MeshBasicMaterial({
-        color: 0x0000ff,
-        wireframe: true,
-    });
-    object = new THREE.Mesh(new THREE.OctahedronGeometry(75, 1), material);
-    object.position.set(0, 0, 200);
-    object.name = 'Octahedron';
-    scene1.add(object);
-
-    // Tetrahedron
-    material = new THREE.MeshBasicMaterial({
-        color: 0xff0000,
-        transparent: true,
-        opacity: 0.5,
-    });
-
-    object = new THREE.Mesh(new THREE.TetrahedronGeometry(75, 0), material);
-    object.position.set(200, 0, 200);
-    object.name = 'Tetrahedron';
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // Buffered geometry primitives
-    // ---------------------------------------------------------------------
-    // Sphere
-    material = new THREE.MeshStandardMaterial({
-        color: 0xffff00,
-        metalness: 0.5,
-        roughness: 1.0,
-        flatShading: true,
-    });
-    material.map = gradientTexture;
-    material.bumpMap = mapGrid;
-    sphere = new THREE.Mesh(new THREE.SphereGeometry(70, 10, 10), material);
-    sphere.position.set(0, 0, 0);
-    sphere.name = 'Sphere';
-    scene1.add(sphere);
-
-    // Cylinder
-    material = new THREE.MeshStandardMaterial({
-        color: 0xff00ff,
-        flatShading: true,
-    });
-    object = new THREE.Mesh(new THREE.CylinderGeometry(10, 80, 100), material);
-    object.position.set(200, 0, 0);
-    object.name = 'Cylinder';
-    scene1.add(object);
-
-    // TorusKnot
-    material = new THREE.MeshStandardMaterial({
-        color: 0xff0000,
-        roughness: 1,
-    });
-    object = new THREE.Mesh(new THREE.TorusKnotGeometry(50, 15, 40, 10), material);
-    object.position.set(-200, 0, 0);
-    object.name = 'Cylinder';
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // Hierarchy
-    // ---------------------------------------------------------------------
-    const mapWood = new THREE.TextureLoader().load('textures/hardwood2_diffuse.jpg');
-    material = new THREE.MeshStandardMaterial({ map: mapWood, side: THREE.DoubleSide });
-
-    object = new THREE.Mesh(new THREE.BoxGeometry(40, 100, 100), material);
-    object.position.set(-200, 0, 400);
-    object.name = 'Cube';
-    scene1.add(object);
-
-    object2 = new THREE.Mesh(new THREE.BoxGeometry(40, 40, 40, 2, 2, 2), material);
-    object2.position.set(0, 0, 50);
-    object2.rotation.set(0, 45, 0);
-    object2.name = 'SubCube';
-    object.add(object2);
-
-    // ---------------------------------------------------------------------
-    // Groups
-    // ---------------------------------------------------------------------
-    const group1 = new THREE.Group();
-    group1.name = 'Group';
-    scene1.add(group1);
-
-    const group2 = new THREE.Group();
-    group2.name = 'subGroup';
-    group2.position.set(0, 50, 0);
-    group1.add(group2);
-
-    object2 = new THREE.Mesh(new THREE.BoxGeometry(30, 30, 30), material);
-    object2.name = 'Cube in group';
-    object2.position.set(0, 0, 400);
-    group2.add(object2);
-
-    // ---------------------------------------------------------------------
-    // THREE.Line Strip
-    // ---------------------------------------------------------------------
-    geometry = new THREE.BufferGeometry();
-    let numPoints = 100;
-    let positions = new Float32Array(numPoints * 3);
-
-    for (let i = 0; i < numPoints; i++) {
-        positions[i * 3] = i;
-        positions[i * 3 + 1] = Math.sin(i / 2) * 20;
-        positions[i * 3 + 2] = 0;
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    object = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xffff00 }));
-    object.position.set(-50, 0, -200);
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // THREE.Line Loop
-    // ---------------------------------------------------------------------
-    geometry = new THREE.BufferGeometry();
-    numPoints = 5;
-    const radius = 70;
-    positions = new Float32Array(numPoints * 3);
-
-    for (let i = 0; i < numPoints; i++) {
-        const s = (i * Math.PI * 2) / numPoints;
-        positions[i * 3] = radius * Math.sin(s);
-        positions[i * 3 + 1] = radius * Math.cos(s);
-        positions[i * 3 + 2] = 0;
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    object = new THREE.LineLoop(geometry, new THREE.LineBasicMaterial({ color: 0xffff00 }));
-    object.position.set(0, 0, -200);
-
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // THREE.Points
-    // ---------------------------------------------------------------------
-    numPoints = 100;
-    const pointsArray = new Float32Array(numPoints * 3);
-    for (let i = 0; i < numPoints; i++) {
-        pointsArray[3 * i] = -50 + Math.random() * 100;
-        pointsArray[3 * i + 1] = Math.random() * 100;
-        pointsArray[3 * i + 2] = -50 + Math.random() * 100;
-    }
-
-    const pointsGeo = new THREE.BufferGeometry();
-    pointsGeo.setAttribute('position', new THREE.BufferAttribute(pointsArray, 3));
-
-    const pointsMaterial = new THREE.PointsMaterial({ color: 0xffff00, size: 5 });
-    const pointCloud = new THREE.Points(pointsGeo, pointsMaterial);
-    pointCloud.name = 'Points';
-    pointCloud.position.set(-200, 0, -200);
-    scene1.add(pointCloud);
-
-    // ---------------------------------------------------------------------
-    // Ortho camera
-    // ---------------------------------------------------------------------
-    const cameraOrtho = new THREE.OrthographicCamera(
-        window.innerWidth / -2,
-        window.innerWidth / 2,
-        window.innerHeight / 2,
-        window.innerHeight / -2,
-        0.1,
-        10,
-    );
-    scene1.add(cameraOrtho);
-    cameraOrtho.name = 'OrthographicCamera';
-
-    material = new THREE.MeshLambertMaterial({
-        color: 0xffff00,
-        side: THREE.DoubleSide,
-    });
-
-    object = new THREE.Mesh(new THREE.CircleGeometry(50, 20, 0, Math.PI * 2), material);
-    object.position.set(200, 0, -400);
-    scene1.add(object);
-
-    object = new THREE.Mesh(new THREE.RingGeometry(10, 50, 20, 5, 0, Math.PI * 2), material);
-    object.position.set(0, 0, -400);
-    scene1.add(object);
-
-    object = new THREE.Mesh(new THREE.CylinderGeometry(25, 75, 100, 40, 5), material);
-    object.position.set(-200, 0, -400);
-    scene1.add(object);
-
-    //
-    const points = [];
-
-    for (let i = 0; i < 50; i++) {
-        points.push(new THREE.Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
-    }
-
-    object = new THREE.Mesh(new THREE.LatheGeometry(points, 20), material);
-    object.position.set(200, 0, 400);
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // Big red box hidden just for testing `onlyVisible` option
-    // ---------------------------------------------------------------------
-    material = new THREE.MeshBasicMaterial({
-        color: 0xff0000,
-    });
-    object = new THREE.Mesh(new THREE.BoxGeometry(200, 200, 200), material);
-    object.position.set(0, 0, 0);
-    object.name = 'CubeHidden';
-    object.visible = false;
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // Model requiring KHR_mesh_quantization
-    // ---------------------------------------------------------------------
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/ShaderBall.glb', function (gltf) {
-        model = gltf.scene;
-        model.scale.setScalar(50);
-        model.position.set(200, -40, -200);
-        scene1.add(model);
-    });
-
-    // ---------------------------------------------------------------------
-    // Model requiring KHR_mesh_quantization
-    // ---------------------------------------------------------------------
-
-    material = new THREE.MeshBasicMaterial({
-        color: 0xffffff,
-    });
-    object = new THREE.InstancedMesh(new THREE.BoxGeometry(10, 10, 10, 2, 2, 2), material, 50);
-    const matrix = new THREE.Matrix4();
-    const color = new THREE.Color();
-    for (let i = 0; i < 50; i++) {
-        matrix.setPosition(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
-        object.setMatrixAt(i, matrix);
-        object.setColorAt(i, color.setHSL(i / 50, 1, 0.5));
-    }
-
-    object.position.set(400, 0, 200);
-    scene1.add(object);
-
-    // ---------------------------------------------------------------------
-    // 2nd THREE.Scene
-    // ---------------------------------------------------------------------
-    scene2 = new THREE.Scene();
-    object = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100), material);
-    object.position.set(0, 0, 0);
-    object.name = 'Cube2ndScene';
-    scene2.name = 'Scene2';
-    scene2.add(object);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    // ---------------------------------------------------------------------
-    // Exporting compressed textures and meshes (KTX2 / Draco / Meshopt)
-    // ---------------------------------------------------------------------
-    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
-
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
-    gltfLoader.setKTX2Loader(ktx2Loader);
-    gltfLoader.setMeshoptDecoder(MeshoptDecoder);
-    gltfLoader.load('coffeemat.glb', function (gltf) {
-        gltf.scene.position.x = 400;
-        gltf.scene.position.z = -200;
-
-        scene1.add(gltf.scene);
-
-        coffeemat = gltf.scene;
-    });
-
-    //
-
-    const gui = new GUI();
-
-    let h = gui.addFolder('Settings');
-    h.add(params, 'trs').name('Use TRS');
-    h.add(params, 'onlyVisible').name('Only Visible Objects');
-    h.add(params, 'binary').name('Binary (GLB)');
-    h.add(params, 'maxTextureSize', 2, 8192).name('Max Texture Size').step(1);
-
-    h = gui.addFolder('Export');
-    h.add(params, 'exportScene1').name('Export Scene 1');
-    h.add(params, 'exportScenes').name('Export Scene 1 and 2');
-    h.add(params, 'exportSphere').name('Export Sphere');
-    h.add(params, 'exportModel').name('Export Model');
-    h.add(params, 'exportObjects').name('Export Sphere With Grid');
-    h.add(params, 'exportSceneObject').name('Export Scene 1 and Object');
-    h.add(params, 'exportCompressedObject').name('Export Coffeemat (from compressed data)');
-
-    gui.open();
-}
-
-function exportScene1() {
-    exportGLTF(scene1);
-}
-
-function exportScenes() {
-    exportGLTF([scene1, scene2]);
-}
-
-function exportSphere() {
-    exportGLTF(sphere);
-}
-
-function exportModel() {
-    exportGLTF(model);
-}
-
-function exportObjects() {
-    exportGLTF([sphere, gridHelper]);
-}
-
-function exportSceneObject() {
-    exportGLTF([scene1, gridHelper]);
-}
-
-function exportCompressedObject() {
-    exportGLTF([coffeemat]);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const timer = Date.now() * 0.0001;
-
-    camera.position.x = Math.cos(timer) * 800;
-    camera.position.z = Math.sin(timer) * 800;
-
-    camera.lookAt(scene1.position);
-    renderer.render(scene1, camera);
-}
diff --git a/examples-testing/examples/misc_exporter_obj.ts b/examples-testing/examples/misc_exporter_obj.ts
deleted file mode 100644
index 025034daf..000000000
--- a/examples-testing/examples/misc_exporter_obj.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJExporter } from 'three/addons/exporters/OBJExporter.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-const params = {
-    addTriangle: addTriangle,
-    addCube: addCube,
-    addCylinder: addCylinder,
-    addMultiple: addMultiple,
-    addTransformed: addTransformed,
-    addPoints: addPoints,
-    exportToObj: exportToObj,
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 400);
-
-    scene = new THREE.Scene();
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
-    directionalLight.position.set(0, 1, 1);
-    scene.add(directionalLight);
-
-    const gui = new GUI();
-
-    let h = gui.addFolder('Geometry Selection');
-    h.add(params, 'addTriangle').name('Triangle');
-    h.add(params, 'addCube').name('Cube');
-    h.add(params, 'addCylinder').name('Cylinder');
-    h.add(params, 'addMultiple').name('Multiple objects');
-    h.add(params, 'addTransformed').name('Transformed objects');
-    h.add(params, 'addPoints').name('Point Cloud');
-
-    h = gui.addFolder('Export');
-    h.add(params, 'exportToObj').name('Export OBJ');
-
-    gui.open();
-
-    addGeometry(1);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enablePan = false;
-}
-
-function exportToObj() {
-    const exporter = new OBJExporter();
-    const result = exporter.parse(scene);
-    saveString(result, 'object.obj');
-}
-
-function addGeometry(type) {
-    for (let i = 0; i < scene.children.length; i++) {
-        const child = scene.children[i];
-
-        if (child.isMesh || child.isPoints) {
-            child.geometry.dispose();
-            scene.remove(child);
-            i--;
-        }
-    }
-
-    if (type === 1) {
-        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
-        const geometry = generateTriangleGeometry();
-
-        scene.add(new THREE.Mesh(geometry, material));
-    } else if (type === 2) {
-        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
-        const geometry = new THREE.BoxGeometry(100, 100, 100);
-        scene.add(new THREE.Mesh(geometry, material));
-    } else if (type === 3) {
-        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
-        const geometry = new THREE.CylinderGeometry(50, 50, 100, 30, 1);
-        scene.add(new THREE.Mesh(geometry, material));
-    } else if (type === 4 || type === 5) {
-        const material = new THREE.MeshLambertMaterial({ color: 0x00cc00 });
-        const geometry = generateTriangleGeometry();
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = -200;
-        scene.add(mesh);
-
-        const geometry2 = new THREE.BoxGeometry(100, 100, 100);
-        const mesh2 = new THREE.Mesh(geometry2, material);
-        scene.add(mesh2);
-
-        const geometry3 = new THREE.CylinderGeometry(50, 50, 100, 30, 1);
-        const mesh3 = new THREE.Mesh(geometry3, material);
-        mesh3.position.x = 200;
-        scene.add(mesh3);
-
-        if (type === 5) {
-            mesh.rotation.y = Math.PI / 4.0;
-            mesh2.rotation.y = Math.PI / 4.0;
-            mesh3.rotation.y = Math.PI / 4.0;
-        }
-    } else if (type === 6) {
-        const points = [0, 0, 0, 100, 0, 0, 100, 100, 0, 0, 100, 0];
-        const colors = [0.5, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0.5, 0];
-
-        const geometry = new THREE.BufferGeometry();
-        geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
-        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-        const material = new THREE.PointsMaterial({ size: 10, vertexColors: true });
-
-        const pointCloud = new THREE.Points(geometry, material);
-        pointCloud.name = 'point cloud';
-        scene.add(pointCloud);
-    }
-}
-
-function addTriangle() {
-    addGeometry(1);
-}
-
-function addCube() {
-    addGeometry(2);
-}
-
-function addCylinder() {
-    addGeometry(3);
-}
-
-function addMultiple() {
-    addGeometry(4);
-}
-
-function addTransformed() {
-    addGeometry(5);
-}
-
-function addPoints() {
-    addGeometry(6);
-}
-
-const link = document.createElement('a');
-link.style.display = 'none';
-document.body.appendChild(link);
-
-function save(blob, filename) {
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-}
-
-function saveString(text, filename) {
-    save(new Blob([text], { type: 'text/plain' }), filename);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
-
-function generateTriangleGeometry() {
-    const geometry = new THREE.BufferGeometry();
-    const vertices = [];
-
-    vertices.push(-50, -50, 0);
-    vertices.push(50, -50, 0);
-    vertices.push(50, 50, 0);
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry.computeVertexNormals();
-
-    return geometry;
-}
diff --git a/examples-testing/examples/misc_exporter_ply.ts b/examples-testing/examples/misc_exporter_ply.ts
deleted file mode 100644
index b7e324688..000000000
--- a/examples-testing/examples/misc_exporter_ply.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { PLYExporter } from 'three/addons/exporters/PLYExporter.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, renderer, exporter, mesh;
-
-const params = {
-    exportASCII: exportASCII,
-    exportBinaryBigEndian: exportBinaryBigEndian,
-    exportBinaryLittleEndian: exportBinaryLittleEndian,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(4, 2, 4);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
-
-    exporter = new PLYExporter();
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(0, 20, 10);
-    directionalLight.castShadow = true;
-    directionalLight.shadow.camera.top = 2;
-    directionalLight.shadow.camera.bottom = -2;
-    directionalLight.shadow.camera.left = -2;
-    directionalLight.shadow.camera.right = 2;
-    scene.add(directionalLight);
-
-    // ground
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(40, 40),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
-    );
-    ground.rotation.x = -Math.PI / 2;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
-    grid.material.opacity = 0.2;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    // export mesh
-
-    const geometry = new THREE.BoxGeometry();
-    const material = new THREE.MeshPhongMaterial({ vertexColors: true });
-
-    // color vertices based on vertex positions
-    const colors = geometry.getAttribute('position').array.slice();
-    for (let i = 0, l = colors.length; i < l; i++) {
-        if (colors[i] > 0) colors[i] = 0.5;
-        else colors[i] = 0;
-    }
-
-    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, false));
-
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.castShadow = true;
-    mesh.position.y = 0.5;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0.5, 0);
-    controls.update();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'exportASCII').name('Export PLY (ASCII)');
-    gui.add(params, 'exportBinaryBigEndian').name('Export PLY (Binary BE)');
-    gui.add(params, 'exportBinaryLittleEndian').name('Export PLY (Binary LE)');
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
-
-function exportASCII() {
-    exporter.parse(mesh, function (result) {
-        saveString(result, 'box.ply');
-    });
-}
-
-function exportBinaryBigEndian() {
-    exporter.parse(
-        mesh,
-        function (result) {
-            saveArrayBuffer(result, 'box.ply');
-        },
-        { binary: true },
-    );
-}
-
-function exportBinaryLittleEndian() {
-    exporter.parse(
-        mesh,
-        function (result) {
-            saveArrayBuffer(result, 'box.ply');
-        },
-        { binary: true, littleEndian: true },
-    );
-}
-
-const link = document.createElement('a');
-link.style.display = 'none';
-document.body.appendChild(link);
-
-function save(blob, filename) {
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-}
-
-function saveString(text, filename) {
-    save(new Blob([text], { type: 'text/plain' }), filename);
-}
-
-function saveArrayBuffer(buffer, filename) {
-    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
-}
diff --git a/examples-testing/examples/misc_exporter_stl.ts b/examples-testing/examples/misc_exporter_stl.ts
deleted file mode 100644
index ff6d6e2b5..000000000
--- a/examples-testing/examples/misc_exporter_stl.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { STLExporter } from 'three/addons/exporters/STLExporter.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, renderer, exporter, mesh;
-
-const params = {
-    exportASCII: exportASCII,
-    exportBinary: exportBinary,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(4, 2, 4);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 4, 20);
-
-    exporter = new STLExporter();
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(0, 20, 10);
-    directionalLight.castShadow = true;
-    directionalLight.shadow.camera.top = 2;
-    directionalLight.shadow.camera.bottom = -2;
-    directionalLight.shadow.camera.left = -2;
-    directionalLight.shadow.camera.right = 2;
-    scene.add(directionalLight);
-
-    // ground
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(40, 40),
-        new THREE.MeshPhongMaterial({ color: 0xbbbbbb, depthWrite: false }),
-    );
-    ground.rotation.x = -Math.PI / 2;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    const grid = new THREE.GridHelper(40, 20, 0x000000, 0x000000);
-    grid.material.opacity = 0.2;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    // export mesh
-
-    const geometry = new THREE.BoxGeometry();
-    const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
-
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.castShadow = true;
-    mesh.position.y = 0.5;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0.5, 0);
-    controls.update();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'exportASCII').name('Export STL (ASCII)');
-    gui.add(params, 'exportBinary').name('Export STL (Binary)');
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
-
-function exportASCII() {
-    const result = exporter.parse(mesh);
-    saveString(result, 'box.stl');
-}
-
-function exportBinary() {
-    const result = exporter.parse(mesh, { binary: true });
-    saveArrayBuffer(result, 'box.stl');
-}
-
-const link = document.createElement('a');
-link.style.display = 'none';
-document.body.appendChild(link);
-
-function save(blob, filename) {
-    link.href = URL.createObjectURL(blob);
-    link.download = filename;
-    link.click();
-}
-
-function saveString(text, filename) {
-    save(new Blob([text], { type: 'text/plain' }), filename);
-}
-
-function saveArrayBuffer(buffer, filename) {
-    save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
-}
diff --git a/examples-testing/examples/misc_exporter_usdz.ts b/examples-testing/examples/misc_exporter_usdz.ts
deleted file mode 100644
index 9a14919ba..000000000
--- a/examples-testing/examples/misc_exporter_usdz.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { USDZExporter } from 'three/addons/exporters/USDZExporter.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-const params = {
-    exportUSDZ: exportUSDZ,
-};
-
-init();
-render();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-2.5, 0.6, 3.0);
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-    scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
-
-    const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-    loader.load('DamagedHelmet.gltf', async function (gltf) {
-        scene.add(gltf.scene);
-
-        const shadowMesh = createSpotShadowMesh();
-        shadowMesh.position.y = -1.1;
-        shadowMesh.position.z = -0.25;
-        shadowMesh.scale.setScalar(2);
-        scene.add(shadowMesh);
-
-        render();
-
-        // USDZ
-
-        const exporter = new USDZExporter();
-        const arraybuffer = await exporter.parseAsync(gltf.scene);
-        const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
-
-        const link = document.getElementById('link');
-        link.href = URL.createObjectURL(blob);
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, -0.15, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-
-    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
-
-    if (isIOS === false) {
-        const gui = new GUI();
-
-        gui.add(params, 'exportUSDZ').name('Export USDZ');
-        gui.open();
-    }
-}
-
-function createSpotShadowMesh() {
-    const canvas = document.createElement('canvas');
-    canvas.width = 128;
-    canvas.height = 128;
-
-    const context = canvas.getContext('2d');
-    const gradient = context.createRadialGradient(
-        canvas.width / 2,
-        canvas.height / 2,
-        0,
-        canvas.width / 2,
-        canvas.height / 2,
-        canvas.width / 2,
-    );
-    gradient.addColorStop(0.1, 'rgba(130,130,130,1)');
-    gradient.addColorStop(1, 'rgba(255,255,255,1)');
-
-    context.fillStyle = gradient;
-    context.fillRect(0, 0, canvas.width, canvas.height);
-
-    const shadowTexture = new THREE.CanvasTexture(canvas);
-
-    const geometry = new THREE.PlaneGeometry();
-    const material = new THREE.MeshBasicMaterial({
-        map: shadowTexture,
-        blending: THREE.MultiplyBlending,
-        toneMapped: false,
-    });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    mesh.rotation.x = -Math.PI / 2;
-
-    return mesh;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function exportUSDZ() {
-    const link = document.getElementById('link');
-    link.click();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_lookat.ts b/examples-testing/examples/misc_lookat.ts
deleted file mode 100644
index 280b6e2d8..000000000
--- a/examples-testing/examples/misc_lookat.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-
-let sphere;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-document.addEventListener('mousemove', onDocumentMouseMove);
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 15000);
-    camera.position.z = 3200;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    sphere = new THREE.Mesh(new THREE.SphereGeometry(100, 20, 20), new THREE.MeshNormalMaterial());
-    scene.add(sphere);
-
-    const geometry = new THREE.CylinderGeometry(0, 10, 100, 12);
-    geometry.rotateX(Math.PI / 2);
-
-    const material = new THREE.MeshNormalMaterial();
-
-    for (let i = 0; i < 1000; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 4000 - 2000;
-        mesh.position.y = Math.random() * 4000 - 2000;
-        mesh.position.z = Math.random() * 4000 - 2000;
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 4 + 2;
-        scene.add(mesh);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) * 10;
-    mouseY = (event.clientY - windowHalfY) * 10;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.0005;
-
-    sphere.position.x = Math.sin(time * 0.7) * 2000;
-    sphere.position.y = Math.cos(time * 0.5) * 2000;
-    sphere.position.z = Math.cos(time * 0.3) * 2000;
-
-    for (let i = 1, l = scene.children.length; i < l; i++) {
-        scene.children[i].lookAt(sphere.position);
-    }
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-    camera.lookAt(scene.position);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/misc_uv_tests.ts b/examples-testing/examples/misc_uv_tests.ts
deleted file mode 100644
index 4f782d45f..000000000
--- a/examples-testing/examples/misc_uv_tests.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as THREE from 'three';
-
-import { UVsDebug } from 'three/addons/utils/UVsDebug.js';
-
-/*
- * This is to help debug UVs problems in geometry,
- * as well as allow a new user to visualize what UVs are about.
- */
-
-function test(name, geometry) {
-    const d = document.createElement('div');
-
-    d.innerHTML = '<h3>' + name + '</h3>';
-
-    d.appendChild(UVsDebug(geometry));
-
-    document.body.appendChild(d);
-}
-
-const points = [];
-
-for (let i = 0; i < 10; i++) {
-    points.push(new THREE.Vector2(Math.sin(i * 0.2) * 15 + 50, (i - 5) * 2));
-}
-
-//
-
-test('new THREE.PlaneGeometry( 100, 100, 4, 4 )', new THREE.PlaneGeometry(100, 100, 4, 4));
-
-test('new THREE.SphereGeometry( 75, 12, 6 )', new THREE.SphereGeometry(75, 12, 6));
-
-test('new THREE.IcosahedronGeometry( 30, 1 )', new THREE.IcosahedronGeometry(30, 1));
-
-test('new THREE.OctahedronGeometry( 30, 2 )', new THREE.OctahedronGeometry(30, 2));
-
-test('new THREE.CylinderGeometry( 25, 75, 100, 10, 5 )', new THREE.CylinderGeometry(25, 75, 100, 10, 5));
-
-test('new THREE.BoxGeometry( 100, 100, 100, 4, 4, 4 )', new THREE.BoxGeometry(100, 100, 100, 4, 4, 4));
-
-test('new THREE.LatheGeometry( points, 8 )', new THREE.LatheGeometry(points, 8));
-
-test('new THREE.TorusGeometry( 50, 20, 8, 8 )', new THREE.TorusGeometry(50, 20, 8, 8));
-
-test('new THREE.TorusKnotGeometry( 50, 10, 12, 6 )', new THREE.TorusKnotGeometry(50, 10, 12, 6));
diff --git a/examples-testing/examples/physics_ammo_instancing.ts b/examples-testing/examples/physics_ammo_instancing.ts
deleted file mode 100644
index 265c254c8..000000000
--- a/examples-testing/examples/physics_ammo_instancing.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-let physics, position;
-
-let boxes, spheres;
-
-init();
-
-async function init() {
-    physics = await AmmoPhysics();
-    position = new THREE.Vector3();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(-1, 1.5, 2);
-    camera.lookAt(0, 0.5, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x666666);
-
-    const hemiLight = new THREE.HemisphereLight();
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(5, 5, 5);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.zoom = 2;
-    scene.add(dirLight);
-
-    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
-    floor.position.y = -2.5;
-    floor.receiveShadow = true;
-    floor.userData.physics = { mass: 0 };
-    scene.add(floor);
-
-    //
-
-    const material = new THREE.MeshLambertMaterial();
-
-    const matrix = new THREE.Matrix4();
-    const color = new THREE.Color();
-
-    // Boxes
-
-    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
-    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
-    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    boxes.castShadow = true;
-    boxes.receiveShadow = true;
-    boxes.userData.physics = { mass: 1 };
-    scene.add(boxes);
-
-    for (let i = 0; i < boxes.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        boxes.setMatrixAt(i, matrix);
-        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    // Spheres
-
-    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
-    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
-    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    spheres.castShadow = true;
-    spheres.receiveShadow = true;
-    spheres.userData.physics = { mass: 1 };
-    scene.add(spheres);
-
-    for (let i = 0; i < spheres.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        spheres.setMatrixAt(i, matrix);
-        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    physics.addScene(scene);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.y = 0.5;
-    controls.update();
-
-    setInterval(() => {
-        let index = Math.floor(Math.random() * boxes.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(boxes, position, index);
-
-        //
-
-        index = Math.floor(Math.random() * spheres.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(spheres, position, index);
-    }, 1000 / 60);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/physics_jolt_instancing.ts b/examples-testing/examples/physics_jolt_instancing.ts
deleted file mode 100644
index 022263c0d..000000000
--- a/examples-testing/examples/physics_jolt_instancing.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { JoltPhysics } from 'three/addons/physics/JoltPhysics.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-let physics, position;
-
-let boxes, spheres;
-
-init();
-
-async function init() {
-    physics = await JoltPhysics();
-    position = new THREE.Vector3();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(-1, 1.5, 2);
-    camera.lookAt(0, 0.5, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x666666);
-
-    const hemiLight = new THREE.HemisphereLight();
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(5, 5, 5);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.zoom = 2;
-    scene.add(dirLight);
-
-    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
-    floor.position.y = -2.5;
-    floor.receiveShadow = true;
-    floor.userData.physics = { mass: 0 };
-    scene.add(floor);
-
-    //
-
-    const material = new THREE.MeshLambertMaterial();
-
-    const matrix = new THREE.Matrix4();
-    const color = new THREE.Color();
-
-    // Boxes
-
-    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
-    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
-    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    boxes.castShadow = true;
-    boxes.receiveShadow = true;
-    boxes.userData.physics = { mass: 1 };
-    scene.add(boxes);
-
-    for (let i = 0; i < boxes.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        boxes.setMatrixAt(i, matrix);
-        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    // Spheres
-
-    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
-    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
-    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    spheres.castShadow = true;
-    spheres.receiveShadow = true;
-    spheres.userData.physics = { mass: 1 };
-    scene.add(spheres);
-
-    for (let i = 0; i < spheres.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        spheres.setMatrixAt(i, matrix);
-        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    physics.addScene(scene);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.y = 0.5;
-    controls.update();
-
-    setInterval(() => {
-        let index = Math.floor(Math.random() * boxes.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(boxes, position, index);
-
-        //
-
-        index = Math.floor(Math.random() * spheres.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(spheres, position, index);
-    }, 1000 / 60);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/physics_rapier_instancing.ts b/examples-testing/examples/physics_rapier_instancing.ts
deleted file mode 100644
index f23cf7667..000000000
--- a/examples-testing/examples/physics_rapier_instancing.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-let physics, position;
-
-let boxes, spheres;
-
-init();
-
-async function init() {
-    physics = await RapierPhysics();
-    position = new THREE.Vector3();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(-1, 1.5, 2);
-    camera.lookAt(0, 0.5, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x666666);
-
-    const hemiLight = new THREE.HemisphereLight();
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(5, 5, 5);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.zoom = 2;
-    scene.add(dirLight);
-
-    const floor = new THREE.Mesh(new THREE.BoxGeometry(10, 5, 10), new THREE.ShadowMaterial({ color: 0x444444 }));
-    floor.position.y = -2.5;
-    floor.receiveShadow = true;
-    floor.userData.physics = { mass: 0 };
-    scene.add(floor);
-
-    //
-
-    const material = new THREE.MeshLambertMaterial();
-
-    const matrix = new THREE.Matrix4();
-    const color = new THREE.Color();
-
-    // Boxes
-
-    const geometryBox = new THREE.BoxGeometry(0.075, 0.075, 0.075);
-    boxes = new THREE.InstancedMesh(geometryBox, material, 400);
-    boxes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    boxes.castShadow = true;
-    boxes.receiveShadow = true;
-    boxes.userData.physics = { mass: 1 };
-    scene.add(boxes);
-
-    for (let i = 0; i < boxes.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        boxes.setMatrixAt(i, matrix);
-        boxes.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    // Spheres
-
-    const geometrySphere = new THREE.IcosahedronGeometry(0.05, 4);
-    spheres = new THREE.InstancedMesh(geometrySphere, material, 400);
-    spheres.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-    spheres.castShadow = true;
-    spheres.receiveShadow = true;
-    spheres.userData.physics = { mass: 1 };
-    scene.add(spheres);
-
-    for (let i = 0; i < spheres.count; i++) {
-        matrix.setPosition(Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5);
-        spheres.setMatrixAt(i, matrix);
-        spheres.setColorAt(i, color.setHex(0xffffff * Math.random()));
-    }
-
-    physics.addScene(scene);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.y = 0.5;
-    controls.update();
-
-    setInterval(() => {
-        let index = Math.floor(Math.random() * boxes.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(boxes, position, index);
-
-        //
-
-        index = Math.floor(Math.random() * spheres.count);
-
-        position.set(0, Math.random() + 1, 0);
-        physics.setMeshPosition(spheres, position, index);
-    }, 1000 / 60);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/svg_lines.ts b/examples-testing/examples/svg_lines.ts
deleted file mode 100644
index 99b74c405..000000000
--- a/examples-testing/examples/svg_lines.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import * as THREE from 'three';
-
-import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js';
-
-THREE.ColorManagement.enabled = false;
-
-let camera, scene, renderer;
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(33, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 10;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0, 0, 0);
-
-    renderer = new SVGRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const vertices = [];
-    const divisions = 50;
-
-    for (let i = 0; i <= divisions; i++) {
-        const v = (i / divisions) * (Math.PI * 2);
-
-        const x = Math.sin(v);
-        const z = Math.cos(v);
-
-        vertices.push(x, 0, z);
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    //
-
-    for (let i = 1; i <= 3; i++) {
-        const material = new THREE.LineBasicMaterial({
-            color: Math.random() * 0xffffff,
-            linewidth: 10,
-        });
-        const line = new THREE.Line(geometry, material);
-        line.scale.setScalar(i / 3);
-        scene.add(line);
-    }
-
-    const material = new THREE.LineDashedMaterial({
-        color: 'blue',
-        linewidth: 1,
-        dashSize: 10,
-        gapSize: 10,
-    });
-    const line = new THREE.Line(geometry, material);
-    line.scale.setScalar(2);
-    scene.add(line);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    let count = 0;
-    const time = performance.now() / 1000;
-
-    scene.traverse(function (child) {
-        child.rotation.x = count + time / 3;
-        child.rotation.z = count + time / 4;
-
-        count++;
-    });
-
-    renderer.render(scene, camera);
-    requestAnimationFrame(animate);
-}
diff --git a/examples-testing/examples/svg_sandbox.ts b/examples-testing/examples/svg_sandbox.ts
deleted file mode 100644
index e6be8386c..000000000
--- a/examples-testing/examples/svg_sandbox.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { SVGRenderer, SVGObject } from 'three/addons/renderers/SVGRenderer.js';
-
-THREE.ColorManagement.enabled = false;
-
-let camera, scene, renderer, stats;
-
-let group;
-
-init();
-animate();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    // QRCODE
-
-    const loader = new THREE.BufferGeometryLoader();
-    loader.load('models/json/QRCode_buffergeometry.json', function (geometry) {
-        mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ vertexColors: true }));
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
-        scene.add(mesh);
-    });
-
-    // CUBES
-
-    const boxGeometry = new THREE.BoxGeometry(100, 100, 100);
-
-    let mesh = new THREE.Mesh(
-        boxGeometry,
-        new THREE.MeshBasicMaterial({ color: 0x0000ff, opacity: 0.5, transparent: true }),
-    );
-    mesh.position.x = 500;
-    mesh.rotation.x = Math.random();
-    mesh.rotation.y = Math.random();
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff }));
-    mesh.position.x = 500;
-    mesh.position.y = 500;
-    mesh.rotation.x = Math.random();
-    mesh.rotation.y = Math.random();
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
-    scene.add(mesh);
-
-    // PLANE
-
-    mesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(100, 100),
-        new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, side: THREE.DoubleSide }),
-    );
-    mesh.position.y = -500;
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
-    scene.add(mesh);
-
-    // CYLINDER
-
-    mesh = new THREE.Mesh(
-        new THREE.CylinderGeometry(20, 100, 200, 10),
-        new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff }),
-    );
-    mesh.position.x = -500;
-    mesh.rotation.x = -Math.PI / 2;
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = 2;
-    scene.add(mesh);
-
-    // POLYFIELD
-
-    const geometry = new THREE.BufferGeometry();
-    const material = new THREE.MeshBasicMaterial({ vertexColors: true, side: THREE.DoubleSide });
-
-    const v = new THREE.Vector3();
-    const v0 = new THREE.Vector3();
-    const v1 = new THREE.Vector3();
-    const v2 = new THREE.Vector3();
-    const color = new THREE.Color();
-
-    const vertices = [];
-    const colors = [];
-
-    for (let i = 0; i < 100; i++) {
-        v.set(Math.random() * 1000 - 500, Math.random() * 1000 - 500, Math.random() * 1000 - 500);
-
-        v0.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
-
-        v1.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
-
-        v2.set(Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50);
-
-        v0.add(v);
-        v1.add(v);
-        v2.add(v);
-
-        color.setHex(Math.random() * 0xffffff);
-
-        // create a single triangle
-
-        vertices.push(v0.x, v0.y, v0.z);
-        vertices.push(v1.x, v1.y, v1.z);
-        vertices.push(v2.x, v2.y, v2.z);
-
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    group = new THREE.Mesh(geometry, material);
-    group.scale.set(2, 2, 2);
-    scene.add(group);
-
-    // SPRITES
-
-    for (let i = 0; i < 50; i++) {
-        const material = new THREE.SpriteMaterial({ color: Math.random() * 0xffffff });
-        const sprite = new THREE.Sprite(material);
-        sprite.position.x = Math.random() * 1000 - 500;
-        sprite.position.y = Math.random() * 1000 - 500;
-        sprite.position.z = Math.random() * 1000 - 500;
-        sprite.scale.set(64, 64, 1);
-        scene.add(sprite);
-    }
-
-    // CUSTOM
-
-    const node = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
-    node.setAttribute('stroke', 'black');
-    node.setAttribute('fill', 'red');
-    node.setAttribute('r', '40');
-
-    for (let i = 0; i < 50; i++) {
-        const object = new SVGObject(node.cloneNode());
-        object.position.x = Math.random() * 1000 - 500;
-        object.position.y = Math.random() * 1000 - 500;
-        object.position.z = Math.random() * 1000 - 500;
-        scene.add(object);
-    }
-
-    // CUSTOM FROM FILE
-
-    const fileLoader = new THREE.FileLoader();
-    fileLoader.load('models/svg/hexagon.svg', function (svg) {
-        const node = document.createElementNS('http://www.w3.org/2000/svg', 'g');
-        const parser = new DOMParser();
-        const doc = parser.parseFromString(svg, 'image/svg+xml');
-
-        node.appendChild(doc.documentElement);
-
-        const object = new SVGObject(node);
-        object.position.x = 500;
-        scene.add(object);
-    });
-
-    // LIGHTS
-
-    const ambient = new THREE.AmbientLight(0x80ffff);
-    scene.add(ambient);
-
-    const directional = new THREE.DirectionalLight(0xffff00);
-    directional.position.set(-1, 0.5, 0);
-    scene.add(directional);
-
-    renderer = new SVGRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setQuality('low');
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.0002;
-
-    camera.position.x = Math.sin(time) * 500;
-    camera.position.z = Math.cos(time) * 500;
-    camera.lookAt(scene.position);
-
-    group.rotation.x += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webaudio_orientation.ts b/examples-testing/examples/webaudio_orientation.ts
deleted file mode 100644
index 7baaa88a0..000000000
--- a/examples-testing/examples/webaudio_orientation.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { PositionalAudioHelper } from 'three/addons/helpers/PositionalAudioHelper.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let scene, camera, renderer;
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', init);
-
-function init() {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    const container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(3, 2, 3);
-
-    const reflectionCube = new THREE.CubeTextureLoader()
-        .setPath('textures/cube/SwedishRoyalCastle/')
-        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 2, 20);
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(5, 5, 0);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.top = 1;
-    dirLight.shadow.camera.bottom = -1;
-    dirLight.shadow.camera.left = -1;
-    dirLight.shadow.camera.right = 1;
-    dirLight.shadow.camera.near = 0.1;
-    dirLight.shadow.camera.far = 20;
-    scene.add(dirLight);
-
-    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
-
-    //
-
-    const mesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(50, 50),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
-    );
-    mesh.rotation.x = -Math.PI / 2;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    const grid = new THREE.GridHelper(50, 50, 0xc1c1c1, 0xc1c1c1);
-    scene.add(grid);
-
-    //
-
-    const listener = new THREE.AudioListener();
-    camera.add(listener);
-
-    const audioElement = document.getElementById('music');
-    audioElement.play();
-
-    const positionalAudio = new THREE.PositionalAudio(listener);
-    positionalAudio.setMediaElementSource(audioElement);
-    positionalAudio.setRefDistance(1);
-    positionalAudio.setDirectionalCone(180, 230, 0.1);
-
-    const helper = new PositionalAudioHelper(positionalAudio, 0.1);
-    positionalAudio.add(helper);
-
-    //
-
-    const gltfLoader = new GLTFLoader();
-    gltfLoader.load('models/gltf/BoomBox.glb', function (gltf) {
-        const boomBox = gltf.scene;
-        boomBox.position.set(0, 0.2, 0);
-        boomBox.scale.set(20, 20, 20);
-
-        boomBox.traverse(function (object) {
-            if (object.isMesh) {
-                object.material.envMap = reflectionCube;
-                object.geometry.rotateY(-Math.PI);
-                object.castShadow = true;
-            }
-        });
-
-        boomBox.add(positionalAudio);
-        scene.add(boomBox);
-
-        renderer.setAnimationLoop(animate);
-    });
-
-    // sound is damped behind this wall
-
-    const wallGeometry = new THREE.BoxGeometry(2, 1, 0.1);
-    const wallMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5 });
-
-    const wall = new THREE.Mesh(wallGeometry, wallMaterial);
-    wall.position.set(0, 0.5, -0.5);
-    scene.add(wall);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0.1, 0);
-    controls.update();
-    controls.minDistance = 0.5;
-    controls.maxDistance = 10;
-    controls.maxPolarAngle = 0.5 * Math.PI;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webaudio_sandbox.ts b/examples-testing/examples/webaudio_sandbox.ts
deleted file mode 100644
index d67d0d552..000000000
--- a/examples-testing/examples/webaudio_sandbox.ts
+++ /dev/null
@@ -1,222 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-
-let camera, controls, scene, renderer, light;
-
-let material1, material2, material3;
-
-let analyser1, analyser2, analyser3;
-
-const clock = new THREE.Clock();
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', init);
-
-function init() {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(0, 25, 0);
-
-    const listener = new THREE.AudioListener();
-    camera.add(listener);
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.FogExp2(0x000000, 0.0025);
-
-    light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 0.5, 1).normalize();
-    scene.add(light);
-
-    const sphere = new THREE.SphereGeometry(20, 32, 16);
-
-    material1 = new THREE.MeshPhongMaterial({ color: 0xffaa00, flatShading: true, shininess: 0 });
-    material2 = new THREE.MeshPhongMaterial({ color: 0xff2200, flatShading: true, shininess: 0 });
-    material3 = new THREE.MeshPhongMaterial({ color: 0x6622aa, flatShading: true, shininess: 0 });
-
-    // sound spheres
-
-    const mesh1 = new THREE.Mesh(sphere, material1);
-    mesh1.position.set(-250, 30, 0);
-    scene.add(mesh1);
-
-    const sound1 = new THREE.PositionalAudio(listener);
-    const songElement = document.getElementById('song');
-    sound1.setMediaElementSource(songElement);
-    sound1.setRefDistance(20);
-    songElement.play();
-    mesh1.add(sound1);
-
-    //
-
-    const mesh2 = new THREE.Mesh(sphere, material2);
-    mesh2.position.set(250, 30, 0);
-    scene.add(mesh2);
-
-    const sound2 = new THREE.PositionalAudio(listener);
-    const skullbeatzElement = document.getElementById('skullbeatz');
-    sound2.setMediaElementSource(skullbeatzElement);
-    sound2.setRefDistance(20);
-    skullbeatzElement.play();
-    mesh2.add(sound2);
-
-    //
-
-    const mesh3 = new THREE.Mesh(sphere, material3);
-    mesh3.position.set(0, 30, -250);
-    scene.add(mesh3);
-
-    const sound3 = new THREE.PositionalAudio(listener);
-    const oscillator = listener.context.createOscillator();
-    oscillator.type = 'sine';
-    oscillator.frequency.setValueAtTime(144, sound3.context.currentTime);
-    oscillator.start(0);
-    sound3.setNodeSource(oscillator);
-    sound3.setRefDistance(20);
-    sound3.setVolume(0.5);
-    mesh3.add(sound3);
-
-    // analysers
-
-    analyser1 = new THREE.AudioAnalyser(sound1, 32);
-    analyser2 = new THREE.AudioAnalyser(sound2, 32);
-    analyser3 = new THREE.AudioAnalyser(sound3, 32);
-
-    // global ambient audio
-
-    const sound4 = new THREE.Audio(listener);
-    const utopiaElement = document.getElementById('utopia');
-    sound4.setMediaElementSource(utopiaElement);
-    sound4.setVolume(0.5);
-    utopiaElement.play();
-
-    // ground
-
-    const helper = new THREE.GridHelper(1000, 10, 0x444444, 0x444444);
-    helper.position.y = 0.1;
-    scene.add(helper);
-
-    //
-
-    const SoundControls = function () {
-        this.master = listener.getMasterVolume();
-        this.firstSphere = sound1.getVolume();
-        this.secondSphere = sound2.getVolume();
-        this.thirdSphere = sound3.getVolume();
-        this.Ambient = sound4.getVolume();
-    };
-
-    const GeneratorControls = function () {
-        this.frequency = oscillator.frequency.value;
-        this.wavetype = oscillator.type;
-    };
-
-    const gui = new GUI();
-    const soundControls = new SoundControls();
-    const generatorControls = new GeneratorControls();
-    const volumeFolder = gui.addFolder('sound volume');
-    const generatorFolder = gui.addFolder('sound generator');
-
-    volumeFolder
-        .add(soundControls, 'master')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(function () {
-            listener.setMasterVolume(soundControls.master);
-        });
-    volumeFolder
-        .add(soundControls, 'firstSphere')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(function () {
-            sound1.setVolume(soundControls.firstSphere);
-        });
-    volumeFolder
-        .add(soundControls, 'secondSphere')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(function () {
-            sound2.setVolume(soundControls.secondSphere);
-        });
-
-    volumeFolder
-        .add(soundControls, 'thirdSphere')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(function () {
-            sound3.setVolume(soundControls.thirdSphere);
-        });
-    volumeFolder
-        .add(soundControls, 'Ambient')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(function () {
-            sound4.setVolume(soundControls.Ambient);
-        });
-    volumeFolder.open();
-    generatorFolder
-        .add(generatorControls, 'frequency')
-        .min(50.0)
-        .max(5000.0)
-        .step(1.0)
-        .onChange(function () {
-            oscillator.frequency.setValueAtTime(generatorControls.frequency, listener.context.currentTime);
-        });
-    generatorFolder
-        .add(generatorControls, 'wavetype', ['sine', 'square', 'sawtooth', 'triangle'])
-        .onChange(function () {
-            oscillator.type = generatorControls.wavetype;
-        });
-
-    generatorFolder.open();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-
-    controls.movementSpeed = 70;
-    controls.lookSpeed = 0.05;
-    controls.lookVertical = false;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    controls.update(delta);
-
-    material1.emissive.b = analyser1.getAverageFrequency() / 256;
-    material2.emissive.b = analyser2.getAverageFrequency() / 256;
-    material3.emissive.b = analyser3.getAverageFrequency() / 256;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webaudio_timing.ts b/examples-testing/examples/webaudio_timing.ts
deleted file mode 100644
index 9e17bcbcd..000000000
--- a/examples-testing/examples/webaudio_timing.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let scene, camera, renderer, clock;
-
-const objects = [];
-
-const speed = 2.5;
-const height = 3;
-const offset = 0.5;
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', init);
-
-function init() {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    const container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(7, 3, 7);
-
-    // lights
-
-    const ambientLight = new THREE.AmbientLight(0xcccccc);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
-    directionalLight.position.set(0, 5, 5);
-    scene.add(directionalLight);
-
-    const d = 5;
-    directionalLight.castShadow = true;
-    directionalLight.shadow.camera.left = -d;
-    directionalLight.shadow.camera.right = d;
-    directionalLight.shadow.camera.top = d;
-    directionalLight.shadow.camera.bottom = -d;
-
-    directionalLight.shadow.camera.near = 1;
-    directionalLight.shadow.camera.far = 20;
-
-    directionalLight.shadow.mapSize.x = 1024;
-    directionalLight.shadow.mapSize.y = 1024;
-
-    // audio
-
-    const audioLoader = new THREE.AudioLoader();
-
-    const listener = new THREE.AudioListener();
-    camera.add(listener);
-
-    // floor
-
-    const floorGeometry = new THREE.PlaneGeometry(10, 10);
-    const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x4676b6 });
-
-    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
-    floor.rotation.x = Math.PI * -0.5;
-    floor.receiveShadow = true;
-    scene.add(floor);
-
-    // objects
-
-    const count = 5;
-    const radius = 3;
-
-    const ballGeometry = new THREE.SphereGeometry(0.3, 32, 16);
-    ballGeometry.translate(0, 0.3, 0);
-    const ballMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
-
-    // create objects when audio buffer is loaded
-
-    audioLoader.load('sounds/ping_pong.mp3', function (buffer) {
-        for (let i = 0; i < count; i++) {
-            const s = (i / count) * Math.PI * 2;
-
-            const ball = new THREE.Mesh(ballGeometry, ballMaterial);
-            ball.castShadow = true;
-            ball.userData.down = false;
-
-            ball.position.x = radius * Math.cos(s);
-            ball.position.z = radius * Math.sin(s);
-
-            const audio = new THREE.PositionalAudio(listener);
-            audio.setBuffer(buffer);
-            ball.add(audio);
-
-            scene.add(ball);
-            objects.push(ball);
-        }
-
-        renderer.setAnimationLoop(animate);
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 25;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = clock.getElapsedTime();
-
-    for (let i = 0; i < objects.length; i++) {
-        const ball = objects[i];
-
-        const previousHeight = ball.position.y;
-        ball.position.y = Math.abs(Math.sin(i * offset + time * speed) * height);
-
-        if (ball.position.y < previousHeight) {
-            ball.userData.down = true;
-        } else {
-            if (ball.userData.down === true) {
-                // ball changed direction from down to up
-
-                const audio = ball.children[0];
-                audio.play(); // play audio with perfect timing when ball hits the surface
-                ball.userData.down = false;
-            }
-        }
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webaudio_visualizer.ts b/examples-testing/examples/webaudio_visualizer.ts
deleted file mode 100644
index a3f58cb36..000000000
--- a/examples-testing/examples/webaudio_visualizer.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as THREE from 'three';
-
-let scene, camera, renderer, analyser, uniforms;
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', init);
-
-function init() {
-    const fftSize = 128;
-
-    //
-
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    //
-
-    const container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.Camera();
-
-    //
-
-    const listener = new THREE.AudioListener();
-
-    const audio = new THREE.Audio(listener);
-    const file = './sounds/376737_Skullbeatz___Bad_Cat_Maste.mp3';
-
-    if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
-        const loader = new THREE.AudioLoader();
-        loader.load(file, function (buffer) {
-            audio.setBuffer(buffer);
-            audio.play();
-        });
-    } else {
-        const mediaElement = new Audio(file);
-        mediaElement.play();
-
-        audio.setMediaElementSource(mediaElement);
-    }
-
-    analyser = new THREE.AudioAnalyser(audio, fftSize);
-
-    //
-
-    uniforms = {
-        tAudioData: { value: new THREE.DataTexture(analyser.data, fftSize / 2, 1, THREE.RedFormat) },
-    };
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-    });
-
-    const geometry = new THREE.PlaneGeometry(1, 1);
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    analyser.getFrequencyData();
-
-    uniforms.tAudioData.value.needsUpdate = true;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_animation_keyframes.ts b/examples-testing/examples/webgl_animation_keyframes.ts
deleted file mode 100644
index 88048f24c..000000000
--- a/examples-testing/examples/webgl_animation_keyframes.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-let mixer;
-
-const clock = new THREE.Clock();
-const container = document.getElementById('container');
-
-const stats = new Stats();
-container.appendChild(stats.dom);
-
-const renderer = new THREE.WebGLRenderer({ antialias: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-container.appendChild(renderer.domElement);
-
-const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-const scene = new THREE.Scene();
-scene.background = new THREE.Color(0xbfe3dd);
-scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
-
-const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
-camera.position.set(5, 2, 8);
-
-const controls = new OrbitControls(camera, renderer.domElement);
-controls.target.set(0, 0.5, 0);
-controls.update();
-controls.enablePan = false;
-controls.enableDamping = true;
-
-const dracoLoader = new DRACOLoader();
-dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
-
-const loader = new GLTFLoader();
-loader.setDRACOLoader(dracoLoader);
-loader.load(
-    'models/gltf/LittlestTokyo.glb',
-    function (gltf) {
-        const model = gltf.scene;
-        model.position.set(1, 1, 0);
-        model.scale.set(0.01, 0.01, 0.01);
-        scene.add(model);
-
-        mixer = new THREE.AnimationMixer(model);
-        mixer.clipAction(gltf.animations[0]).play();
-
-        renderer.setAnimationLoop(animate);
-    },
-    undefined,
-    function (e) {
-        console.error(e);
-    },
-);
-
-window.onresize = function () {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-};
-
-function animate() {
-    const delta = clock.getDelta();
-
-    mixer.update(delta);
-
-    controls.update();
-
-    stats.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_animation_multiple.ts b/examples-testing/examples/webgl_animation_multiple.ts
deleted file mode 100644
index 152c65067..000000000
--- a/examples-testing/examples/webgl_animation_multiple.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, clock;
-let model, animations;
-
-const mixers = [],
-    objects = [];
-
-const params = {
-    sharedSkeleton: false,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(2, 3, -6);
-    camera.lookAt(0, 1, 0);
-
-    clock = new THREE.Clock();
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(-3, 10, -10);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.top = 4;
-    dirLight.shadow.camera.bottom = -4;
-    dirLight.shadow.camera.left = -4;
-    dirLight.shadow.camera.right = 4;
-    dirLight.shadow.camera.near = 0.1;
-    dirLight.shadow.camera.far = 40;
-    scene.add(dirLight);
-
-    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
-
-    // ground
-
-    const mesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(200, 200),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
-    );
-    mesh.rotation.x = -Math.PI / 2;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/Soldier.glb', function (gltf) {
-        model = gltf.scene;
-        animations = gltf.animations;
-
-        model.traverse(function (object) {
-            if (object.isMesh) object.castShadow = true;
-        });
-
-        setupDefaultScene();
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'sharedSkeleton').onChange(function () {
-        clearScene();
-
-        if (params.sharedSkeleton === true) {
-            setupSharedSkeletonScene();
-        } else {
-            setupDefaultScene();
-        }
-    });
-    gui.open();
-}
-
-function clearScene() {
-    for (const mixer of mixers) {
-        mixer.stopAllAction();
-    }
-
-    mixers.length = 0;
-
-    //
-
-    for (const object of objects) {
-        scene.remove(object);
-
-        scene.traverse(function (child) {
-            if (child.isSkinnedMesh) child.skeleton.dispose();
-        });
-    }
-}
-
-function setupDefaultScene() {
-    // three cloned models with independent skeletons.
-    // each model can have its own animation state
-
-    const model1 = SkeletonUtils.clone(model);
-    const model2 = SkeletonUtils.clone(model);
-    const model3 = SkeletonUtils.clone(model);
-
-    model1.position.x = -2;
-    model2.position.x = 0;
-    model3.position.x = 2;
-
-    const mixer1 = new THREE.AnimationMixer(model1);
-    const mixer2 = new THREE.AnimationMixer(model2);
-    const mixer3 = new THREE.AnimationMixer(model3);
-
-    mixer1.clipAction(animations[0]).play(); // idle
-    mixer2.clipAction(animations[1]).play(); // run
-    mixer3.clipAction(animations[3]).play(); // walk
-
-    scene.add(model1, model2, model3);
-
-    objects.push(model1, model2, model3);
-    mixers.push(mixer1, mixer2, mixer3);
-}
-
-function setupSharedSkeletonScene() {
-    // three cloned models with a single shared skeleton.
-    // all models share the same animation state
-
-    const sharedModel = SkeletonUtils.clone(model);
-    const shareSkinnedMesh = sharedModel.getObjectByName('vanguard_Mesh');
-    const sharedSkeleton = shareSkinnedMesh.skeleton;
-    const sharedParentBone = sharedModel.getObjectByName('mixamorigHips');
-    scene.add(sharedParentBone); // the bones need to be in the scene for the animation to work
-
-    const model1 = shareSkinnedMesh.clone();
-    const model2 = shareSkinnedMesh.clone();
-    const model3 = shareSkinnedMesh.clone();
-
-    model1.bindMode = THREE.DetachedBindMode;
-    model2.bindMode = THREE.DetachedBindMode;
-    model3.bindMode = THREE.DetachedBindMode;
-
-    const identity = new THREE.Matrix4();
-
-    model1.bind(sharedSkeleton, identity);
-    model2.bind(sharedSkeleton, identity);
-    model3.bind(sharedSkeleton, identity);
-
-    model1.position.x = -2;
-    model2.position.x = 0;
-    model3.position.x = 2;
-
-    // apply transformation from the glTF asset
-
-    model1.scale.setScalar(0.01);
-    model1.rotation.x = -Math.PI * 0.5;
-    model2.scale.setScalar(0.01);
-    model2.rotation.x = -Math.PI * 0.5;
-    model3.scale.setScalar(0.01);
-    model3.rotation.x = -Math.PI * 0.5;
-
-    //
-
-    const mixer = new THREE.AnimationMixer(sharedParentBone);
-    mixer.clipAction(animations[1]).play();
-
-    scene.add(sharedParentBone, model1, model2, model3);
-
-    objects.push(sharedParentBone, model1, model2, model3);
-    mixers.push(mixer);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    for (const mixer of mixers) mixer.update(delta);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_animation_skinning_morph.ts b/examples-testing/examples/webgl_animation_skinning_morph.ts
deleted file mode 100644
index f05369aa9..000000000
--- a/examples-testing/examples/webgl_animation_skinning_morph.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let container, stats, clock, gui, mixer, actions, activeAction, previousAction;
-let camera, scene, renderer, model, face;
-
-const api = { state: 'Walking' };
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 100);
-    camera.position.set(-5, 3, 10);
-    camera.lookAt(0, 2, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xe0e0e0);
-    scene.fog = new THREE.Fog(0xe0e0e0, 20, 100);
-
-    clock = new THREE.Clock();
-
-    // lights
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
-    hemiLight.position.set(0, 20, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(0, 20, 10);
-    scene.add(dirLight);
-
-    // ground
-
-    const mesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(2000, 2000),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
-    );
-    mesh.rotation.x = -Math.PI / 2;
-    scene.add(mesh);
-
-    const grid = new THREE.GridHelper(200, 40, 0x000000, 0x000000);
-    grid.material.opacity = 0.2;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    // model
-
-    const loader = new GLTFLoader();
-    loader.load(
-        'models/gltf/RobotExpressive/RobotExpressive.glb',
-        function (gltf) {
-            model = gltf.scene;
-            scene.add(model);
-
-            createGUI(model, gltf.animations);
-        },
-        undefined,
-        function (e) {
-            console.error(e);
-        },
-    );
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // stats
-    stats = new Stats();
-    container.appendChild(stats.dom);
-}
-
-function createGUI(model, animations) {
-    const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
-    const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];
-
-    gui = new GUI();
-
-    mixer = new THREE.AnimationMixer(model);
-
-    actions = {};
-
-    for (let i = 0; i < animations.length; i++) {
-        const clip = animations[i];
-        const action = mixer.clipAction(clip);
-        actions[clip.name] = action;
-
-        if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) {
-            action.clampWhenFinished = true;
-            action.loop = THREE.LoopOnce;
-        }
-    }
-
-    // states
-
-    const statesFolder = gui.addFolder('States');
-
-    const clipCtrl = statesFolder.add(api, 'state').options(states);
-
-    clipCtrl.onChange(function () {
-        fadeToAction(api.state, 0.5);
-    });
-
-    statesFolder.open();
-
-    // emotes
-
-    const emoteFolder = gui.addFolder('Emotes');
-
-    function createEmoteCallback(name) {
-        api[name] = function () {
-            fadeToAction(name, 0.2);
-
-            mixer.addEventListener('finished', restoreState);
-        };
-
-        emoteFolder.add(api, name);
-    }
-
-    function restoreState() {
-        mixer.removeEventListener('finished', restoreState);
-
-        fadeToAction(api.state, 0.2);
-    }
-
-    for (let i = 0; i < emotes.length; i++) {
-        createEmoteCallback(emotes[i]);
-    }
-
-    emoteFolder.open();
-
-    // expressions
-
-    face = model.getObjectByName('Head_4');
-
-    const expressions = Object.keys(face.morphTargetDictionary);
-    const expressionFolder = gui.addFolder('Expressions');
-
-    for (let i = 0; i < expressions.length; i++) {
-        expressionFolder.add(face.morphTargetInfluences, i, 0, 1, 0.01).name(expressions[i]);
-    }
-
-    activeAction = actions['Walking'];
-    activeAction.play();
-
-    expressionFolder.open();
-}
-
-function fadeToAction(name, duration) {
-    previousAction = activeAction;
-    activeAction = actions[name];
-
-    if (previousAction !== activeAction) {
-        previousAction.fadeOut(duration);
-    }
-
-    activeAction.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn(duration).play();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const dt = clock.getDelta();
-
-    if (mixer) mixer.update(dt);
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry.ts b/examples-testing/examples/webgl_buffergeometry.ts
deleted file mode 100644
index 28b2c96a4..000000000
--- a/examples-testing/examples/webgl_buffergeometry.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let mesh;
-
-init();
-animate();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
-    light1.position.set(1, 1, 1);
-    scene.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
-    light2.position.set(0, -1, 0);
-    scene.add(light2);
-
-    //
-
-    const triangles = 160000;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const normals = [];
-    const colors = [];
-
-    const color = new THREE.Color();
-
-    const n = 800,
-        n2 = n / 2; // triangles spread in the cube
-    const d = 12,
-        d2 = d / 2; // individual triangle size
-
-    const pA = new THREE.Vector3();
-    const pB = new THREE.Vector3();
-    const pC = new THREE.Vector3();
-
-    const cb = new THREE.Vector3();
-    const ab = new THREE.Vector3();
-
-    for (let i = 0; i < triangles; i++) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        const ax = x + Math.random() * d - d2;
-        const ay = y + Math.random() * d - d2;
-        const az = z + Math.random() * d - d2;
-
-        const bx = x + Math.random() * d - d2;
-        const by = y + Math.random() * d - d2;
-        const bz = z + Math.random() * d - d2;
-
-        const cx = x + Math.random() * d - d2;
-        const cy = y + Math.random() * d - d2;
-        const cz = z + Math.random() * d - d2;
-
-        positions.push(ax, ay, az);
-        positions.push(bx, by, bz);
-        positions.push(cx, cy, cz);
-
-        // flat face normals
-
-        pA.set(ax, ay, az);
-        pB.set(bx, by, bz);
-        pC.set(cx, cy, cz);
-
-        cb.subVectors(pC, pB);
-        ab.subVectors(pA, pB);
-        cb.cross(ab);
-
-        cb.normalize();
-
-        const nx = cb.x;
-        const ny = cb.y;
-        const nz = cb.z;
-
-        normals.push(nx, ny, nz);
-        normals.push(nx, ny, nz);
-        normals.push(nx, ny, nz);
-
-        // colors
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz);
-
-        const alpha = Math.random();
-
-        colors.push(color.r, color.g, color.b, alpha);
-        colors.push(color.r, color.g, color.b, alpha);
-        colors.push(color.r, color.g, color.b, alpha);
-    }
-
-    function disposeArray() {
-        this.array = null;
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3).onUpload(disposeArray));
-    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3).onUpload(disposeArray));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4).onUpload(disposeArray));
-
-    geometry.computeBoundingSphere();
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xd5d5d5,
-        specular: 0xffffff,
-        shininess: 250,
-        side: THREE.DoubleSide,
-        vertexColors: true,
-        transparent: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.25;
-    mesh.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts b/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
deleted file mode 100644
index 96926c2c3..000000000
--- a/examples-testing/examples/webgl_buffergeometry_attributes_integer.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, mesh;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 2500;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    // geometry
-
-    const triangles = 10000;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const uvs = [];
-    const textureIndices = [];
-
-    const n = 800,
-        n2 = n / 2; // triangles spread in the cube
-    const d = 50,
-        d2 = d / 2; // individual triangle size
-
-    for (let i = 0; i < triangles; i++) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        const ax = x + Math.random() * d - d2;
-        const ay = y + Math.random() * d - d2;
-        const az = z + Math.random() * d - d2;
-
-        const bx = x + Math.random() * d - d2;
-        const by = y + Math.random() * d - d2;
-        const bz = z + Math.random() * d - d2;
-
-        const cx = x + Math.random() * d - d2;
-        const cy = y + Math.random() * d - d2;
-        const cz = z + Math.random() * d - d2;
-
-        positions.push(ax, ay, az);
-        positions.push(bx, by, bz);
-        positions.push(cx, cy, cz);
-
-        // uvs
-
-        uvs.push(0, 0);
-        uvs.push(0.5, 1);
-        uvs.push(1, 0);
-
-        // texture indices
-
-        const t = i % 3;
-        textureIndices.push(t, t, t);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
-    geometry.setAttribute('textureIndex', new THREE.Int16BufferAttribute(textureIndices, 1));
-    geometry.attributes.textureIndex.gpuType = THREE.IntType;
-
-    geometry.computeBoundingSphere();
-
-    // material
-
-    const loader = new THREE.TextureLoader();
-
-    const map1 = loader.load('textures/crate.gif');
-    const map2 = loader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
-    const map3 = loader.load('textures/terrain/grasslight-big.jpg');
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            uTextures: {
-                value: [map1, map2, map3],
-            },
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        side: THREE.DoubleSide,
-        glslVersion: THREE.GLSL3,
-    });
-
-    // mesh
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-}
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.25;
-    mesh.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_attributes_none.ts b/examples-testing/examples/webgl_buffergeometry_attributes_none.ts
deleted file mode 100644
index a1424e871..000000000
--- a/examples-testing/examples/webgl_buffergeometry_attributes_none.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, mesh;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 4;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    // geometry
-
-    const triangleCount = 10000;
-    const vertexCountPerTriangle = 3;
-    const vertexCount = triangleCount * vertexCountPerTriangle;
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setDrawRange(0, vertexCount);
-
-    // material
-
-    const material = new THREE.RawShaderMaterial({
-        uniforms: {
-            seed: { value: 42 },
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        side: THREE.DoubleSide,
-        glslVersion: THREE.GLSL3,
-    });
-
-    // mesh
-
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.frustumCulled = false;
-    scene.add(mesh);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-}
-
-function animate(time) {
-    mesh.rotation.x = (time / 1000.0) * 0.25;
-    mesh.rotation.y = (time / 1000.0) * 0.5;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts b/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
deleted file mode 100644
index 0dffa65cc..000000000
--- a/examples-testing/examples/webgl_buffergeometry_custom_attributes_particles.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let renderer, scene, camera, stats;
-
-let particleSystem, uniforms, geometry;
-
-const particles = 100000;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-
-    uniforms = {
-        pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/spark1.png') },
-    };
-
-    const shaderMaterial = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-
-        blending: THREE.AdditiveBlending,
-        depthTest: false,
-        transparent: true,
-        vertexColors: true,
-    });
-
-    const radius = 200;
-
-    geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const colors = [];
-    const sizes = [];
-
-    const color = new THREE.Color();
-
-    for (let i = 0; i < particles; i++) {
-        positions.push((Math.random() * 2 - 1) * radius);
-        positions.push((Math.random() * 2 - 1) * radius);
-        positions.push((Math.random() * 2 - 1) * radius);
-
-        color.setHSL(i / particles, 1.0, 0.5);
-
-        colors.push(color.r, color.g, color.b);
-
-        sizes.push(20);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1).setUsage(THREE.DynamicDrawUsage));
-
-    particleSystem = new THREE.Points(geometry, shaderMaterial);
-
-    scene.add(particleSystem);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = Date.now() * 0.005;
-
-    particleSystem.rotation.z = 0.01 * time;
-
-    const sizes = geometry.attributes.size.array;
-
-    for (let i = 0; i < particles; i++) {
-        sizes[i] = 10 * (1 + Math.sin(0.1 * i + time));
-    }
-
-    geometry.attributes.size.needsUpdate = true;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_drawrange.ts b/examples-testing/examples/webgl_buffergeometry_drawrange.ts
deleted file mode 100644
index 142ff43bf..000000000
--- a/examples-testing/examples/webgl_buffergeometry_drawrange.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let group;
-let container, stats;
-const particlesData = [];
-let camera, scene, renderer;
-let positions, colors;
-let particles;
-let pointCloud;
-let particlePositions;
-let linesMesh;
-
-const maxParticleCount = 1000;
-let particleCount = 500;
-const r = 800;
-const rHalf = r / 2;
-
-const effectController = {
-    showDots: true,
-    showLines: true,
-    minDistance: 150,
-    limitConnections: false,
-    maxConnections: 20,
-    particleCount: 500,
-};
-
-init();
-
-function initGUI() {
-    const gui = new GUI();
-
-    gui.add(effectController, 'showDots').onChange(function (value) {
-        pointCloud.visible = value;
-    });
-    gui.add(effectController, 'showLines').onChange(function (value) {
-        linesMesh.visible = value;
-    });
-    gui.add(effectController, 'minDistance', 10, 300);
-    gui.add(effectController, 'limitConnections');
-    gui.add(effectController, 'maxConnections', 0, 30, 1);
-    gui.add(effectController, 'particleCount', 0, maxParticleCount, 1).onChange(function (value) {
-        particleCount = value;
-        particles.setDrawRange(0, particleCount);
-    });
-}
-
-function init() {
-    initGUI();
-
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
-    camera.position.z = 1750;
-
-    const controls = new OrbitControls(camera, container);
-    controls.minDistance = 1000;
-    controls.maxDistance = 3000;
-
-    scene = new THREE.Scene();
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const helper = new THREE.BoxHelper(new THREE.Mesh(new THREE.BoxGeometry(r, r, r)));
-    helper.material.color.setHex(0x474747);
-    helper.material.blending = THREE.AdditiveBlending;
-    helper.material.transparent = true;
-    group.add(helper);
-
-    const segments = maxParticleCount * maxParticleCount;
-
-    positions = new Float32Array(segments * 3);
-    colors = new Float32Array(segments * 3);
-
-    const pMaterial = new THREE.PointsMaterial({
-        color: 0xffffff,
-        size: 3,
-        blending: THREE.AdditiveBlending,
-        transparent: true,
-        sizeAttenuation: false,
-    });
-
-    particles = new THREE.BufferGeometry();
-    particlePositions = new Float32Array(maxParticleCount * 3);
-
-    for (let i = 0; i < maxParticleCount; i++) {
-        const x = Math.random() * r - r / 2;
-        const y = Math.random() * r - r / 2;
-        const z = Math.random() * r - r / 2;
-
-        particlePositions[i * 3] = x;
-        particlePositions[i * 3 + 1] = y;
-        particlePositions[i * 3 + 2] = z;
-
-        // add it to the geometry
-        particlesData.push({
-            velocity: new THREE.Vector3(-1 + Math.random() * 2, -1 + Math.random() * 2, -1 + Math.random() * 2),
-            numConnections: 0,
-        });
-    }
-
-    particles.setDrawRange(0, particleCount);
-    particles.setAttribute(
-        'position',
-        new THREE.BufferAttribute(particlePositions, 3).setUsage(THREE.DynamicDrawUsage),
-    );
-
-    // create the particle system
-    pointCloud = new THREE.Points(particles, pMaterial);
-    group.add(pointCloud);
-
-    const geometry = new THREE.BufferGeometry();
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3).setUsage(THREE.DynamicDrawUsage));
-    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage));
-
-    geometry.computeBoundingSphere();
-
-    geometry.setDrawRange(0, 0);
-
-    const material = new THREE.LineBasicMaterial({
-        vertexColors: true,
-        blending: THREE.AdditiveBlending,
-        transparent: true,
-    });
-
-    linesMesh = new THREE.LineSegments(geometry, material);
-    group.add(linesMesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    let vertexpos = 0;
-    let colorpos = 0;
-    let numConnected = 0;
-
-    for (let i = 0; i < particleCount; i++) particlesData[i].numConnections = 0;
-
-    for (let i = 0; i < particleCount; i++) {
-        // get the particle
-        const particleData = particlesData[i];
-
-        particlePositions[i * 3] += particleData.velocity.x;
-        particlePositions[i * 3 + 1] += particleData.velocity.y;
-        particlePositions[i * 3 + 2] += particleData.velocity.z;
-
-        if (particlePositions[i * 3 + 1] < -rHalf || particlePositions[i * 3 + 1] > rHalf)
-            particleData.velocity.y = -particleData.velocity.y;
-
-        if (particlePositions[i * 3] < -rHalf || particlePositions[i * 3] > rHalf)
-            particleData.velocity.x = -particleData.velocity.x;
-
-        if (particlePositions[i * 3 + 2] < -rHalf || particlePositions[i * 3 + 2] > rHalf)
-            particleData.velocity.z = -particleData.velocity.z;
-
-        if (effectController.limitConnections && particleData.numConnections >= effectController.maxConnections)
-            continue;
-
-        // Check collision
-        for (let j = i + 1; j < particleCount; j++) {
-            const particleDataB = particlesData[j];
-            if (effectController.limitConnections && particleDataB.numConnections >= effectController.maxConnections)
-                continue;
-
-            const dx = particlePositions[i * 3] - particlePositions[j * 3];
-            const dy = particlePositions[i * 3 + 1] - particlePositions[j * 3 + 1];
-            const dz = particlePositions[i * 3 + 2] - particlePositions[j * 3 + 2];
-            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
-
-            if (dist < effectController.minDistance) {
-                particleData.numConnections++;
-                particleDataB.numConnections++;
-
-                const alpha = 1.0 - dist / effectController.minDistance;
-
-                positions[vertexpos++] = particlePositions[i * 3];
-                positions[vertexpos++] = particlePositions[i * 3 + 1];
-                positions[vertexpos++] = particlePositions[i * 3 + 2];
-
-                positions[vertexpos++] = particlePositions[j * 3];
-                positions[vertexpos++] = particlePositions[j * 3 + 1];
-                positions[vertexpos++] = particlePositions[j * 3 + 2];
-
-                colors[colorpos++] = alpha;
-                colors[colorpos++] = alpha;
-                colors[colorpos++] = alpha;
-
-                colors[colorpos++] = alpha;
-                colors[colorpos++] = alpha;
-                colors[colorpos++] = alpha;
-
-                numConnected++;
-            }
-        }
-    }
-
-    linesMesh.geometry.setDrawRange(0, numConnected * 2);
-    linesMesh.geometry.attributes.position.needsUpdate = true;
-    linesMesh.geometry.attributes.color.needsUpdate = true;
-
-    pointCloud.geometry.attributes.position.needsUpdate = true;
-
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.001;
-
-    group.rotation.y = time * 0.1;
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts b/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
deleted file mode 100644
index aea462cf4..000000000
--- a/examples-testing/examples/webgl_buffergeometry_glbufferattribute.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let points;
-
-const particles = 300000;
-let drawCount = 10000;
-
-init();
-animate();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const positions2 = [];
-    const colors = [];
-
-    const color = new THREE.Color();
-
-    const n = 1000,
-        n2 = n / 2; // particles spread in the cube
-
-    for (let i = 0; i < particles; i++) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        positions.push(x, y, z);
-        positions2.push(z * 0.3, x * 0.3, y * 0.3);
-
-        // colors
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
-
-        colors.push(color.r, color.g, color.b);
-    }
-
-    const gl = renderer.getContext();
-
-    const pos = gl.createBuffer();
-    gl.bindBuffer(gl.ARRAY_BUFFER, pos);
-    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
-
-    const pos2 = gl.createBuffer();
-    gl.bindBuffer(gl.ARRAY_BUFFER, pos2);
-    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions2), gl.STATIC_DRAW);
-
-    const rgb = gl.createBuffer();
-    gl.bindBuffer(gl.ARRAY_BUFFER, rgb);
-    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
-
-    const posAttr1 = new THREE.GLBufferAttribute(pos, gl.FLOAT, 3, 4, particles);
-    const posAttr2 = new THREE.GLBufferAttribute(pos2, gl.FLOAT, 3, 4, particles);
-    geometry.setAttribute('position', posAttr1);
-
-    setInterval(function () {
-        const attr = geometry.getAttribute('position');
-
-        geometry.setAttribute('position', attr === posAttr1 ? posAttr2 : posAttr1);
-    }, 2000);
-
-    geometry.setAttribute('color', new THREE.GLBufferAttribute(rgb, gl.FLOAT, 3, 4, particles));
-
-    //
-
-    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
-
-    points = new THREE.Points(geometry, material);
-
-    geometry.boundingSphere = new THREE.Sphere().set(new THREE.Vector3(), 500);
-
-    scene.add(points);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    drawCount = (Math.max(5000, drawCount) + Math.floor(500 * Math.random())) % particles;
-    points.geometry.setDrawRange(0, drawCount);
-
-    const time = Date.now() * 0.001;
-
-    points.rotation.x = time * 0.1;
-    points.rotation.y = time * 0.2;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_indexed.ts b/examples-testing/examples/webgl_buffergeometry_indexed.ts
deleted file mode 100644
index a2f9f3795..000000000
--- a/examples-testing/examples/webgl_buffergeometry_indexed.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats;
-
-let mesh;
-
-init();
-
-function init() {
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 64;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    //
-
-    const light = new THREE.HemisphereLight();
-    light.intensity = 3;
-    scene.add(light);
-
-    //
-
-    const geometry = new THREE.BufferGeometry();
-
-    const indices = [];
-
-    const vertices = [];
-    const normals = [];
-    const colors = [];
-
-    const size = 20;
-    const segments = 10;
-
-    const halfSize = size / 2;
-    const segmentSize = size / segments;
-
-    const _color = new THREE.Color();
-
-    // generate vertices, normals and color data for a simple grid geometry
-
-    for (let i = 0; i <= segments; i++) {
-        const y = i * segmentSize - halfSize;
-
-        for (let j = 0; j <= segments; j++) {
-            const x = j * segmentSize - halfSize;
-
-            vertices.push(x, -y, 0);
-            normals.push(0, 0, 1);
-
-            const r = x / size + 0.5;
-            const g = y / size + 0.5;
-
-            _color.setRGB(r, g, 1, THREE.SRGBColorSpace);
-
-            colors.push(_color.r, _color.g, _color.b);
-        }
-    }
-
-    // generate indices (data for element array buffer)
-
-    for (let i = 0; i < segments; i++) {
-        for (let j = 0; j < segments; j++) {
-            const a = i * (segments + 1) + (j + 1);
-            const b = i * (segments + 1) + j;
-            const c = (i + 1) * (segments + 1) + j;
-            const d = (i + 1) * (segments + 1) + (j + 1);
-
-            // generate two faces (triangles) per iteration
-
-            indices.push(a, b, d); // face one
-            indices.push(b, c, d); // face two
-        }
-    }
-
-    //
-
-    geometry.setIndex(indices);
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    const material = new THREE.MeshPhongMaterial({
-        side: THREE.DoubleSide,
-        vertexColors: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const gui = new GUI();
-    gui.add(material, 'wireframe');
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.25;
-    mesh.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing.ts b/examples-testing/examples/webgl_buffergeometry_instancing.ts
deleted file mode 100644
index a5b90ae6e..000000000
--- a/examples-testing/examples/webgl_buffergeometry_instancing.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-
-    // geometry
-
-    const vector = new THREE.Vector4();
-
-    const instances = 50000;
-
-    const positions = [];
-    const offsets = [];
-    const colors = [];
-    const orientationsStart = [];
-    const orientationsEnd = [];
-
-    positions.push(0.025, -0.025, 0);
-    positions.push(-0.025, 0.025, 0);
-    positions.push(0, 0, 0.025);
-
-    // instanced attributes
-
-    for (let i = 0; i < instances; i++) {
-        // offsets
-
-        offsets.push(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
-
-        // colors
-
-        colors.push(Math.random(), Math.random(), Math.random(), Math.random());
-
-        // orientation start
-
-        vector.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
-        vector.normalize();
-
-        orientationsStart.push(vector.x, vector.y, vector.z, vector.w);
-
-        // orientation end
-
-        vector.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
-        vector.normalize();
-
-        orientationsEnd.push(vector.x, vector.y, vector.z, vector.w);
-    }
-
-    const geometry = new THREE.InstancedBufferGeometry();
-    geometry.instanceCount = instances; // set so its initalized for dat.GUI, will be set in first draw otherwise
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-
-    geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(new Float32Array(offsets), 3));
-    geometry.setAttribute('color', new THREE.InstancedBufferAttribute(new Float32Array(colors), 4));
-    geometry.setAttribute(
-        'orientationStart',
-        new THREE.InstancedBufferAttribute(new Float32Array(orientationsStart), 4),
-    );
-    geometry.setAttribute('orientationEnd', new THREE.InstancedBufferAttribute(new Float32Array(orientationsEnd), 4));
-
-    // material
-
-    const material = new THREE.RawShaderMaterial({
-        uniforms: {
-            time: { value: 1.0 },
-            sineTime: { value: 1.0 },
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        side: THREE.DoubleSide,
-        forceSinglePass: true,
-        transparent: true,
-    });
-
-    //
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const gui = new GUI({ width: 350 });
-    gui.add(geometry, 'instanceCount', 0, instances);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = performance.now();
-
-    const object = scene.children[0];
-
-    object.rotation.y = time * 0.0005;
-    object.material.uniforms['time'].value = time * 0.005;
-    object.material.uniforms['sineTime'].value = Math.sin(object.material.uniforms['time'].value * 0.05);
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts b/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
deleted file mode 100644
index 2158dff39..000000000
--- a/examples-testing/examples/webgl_buffergeometry_instancing_billboards.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-let geometry, material, mesh;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.z = 1400;
-
-    scene = new THREE.Scene();
-
-    const circleGeometry = new THREE.CircleGeometry(1, 6);
-
-    geometry = new THREE.InstancedBufferGeometry();
-    geometry.index = circleGeometry.index;
-    geometry.attributes = circleGeometry.attributes;
-
-    const particleCount = 75000;
-
-    const translateArray = new Float32Array(particleCount * 3);
-
-    for (let i = 0, i3 = 0, l = particleCount; i < l; i++, i3 += 3) {
-        translateArray[i3 + 0] = Math.random() * 2 - 1;
-        translateArray[i3 + 1] = Math.random() * 2 - 1;
-        translateArray[i3 + 2] = Math.random() * 2 - 1;
-    }
-
-    geometry.setAttribute('translate', new THREE.InstancedBufferAttribute(translateArray, 3));
-
-    material = new THREE.RawShaderMaterial({
-        uniforms: {
-            map: { value: new THREE.TextureLoader().load('textures/sprites/circle.png') },
-            time: { value: 0.0 },
-        },
-        vertexShader: document.getElementById('vshader').textContent,
-        fragmentShader: document.getElementById('fshader').textContent,
-        depthTest: true,
-        depthWrite: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.scale.set(500, 500, 500);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    return true;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = performance.now() * 0.0005;
-
-    material.uniforms['time'].value = time;
-
-    mesh.rotation.x = time * 0.2;
-    mesh.rotation.y = time * 0.4;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts b/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
deleted file mode 100644
index bef2c264d..000000000
--- a/examples-testing/examples/webgl_buffergeometry_instancing_interleaved.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-let camera, scene, renderer, mesh;
-
-const instances = 5000;
-let lastTime = 0;
-
-const moveQ = new THREE.Quaternion(0.5, 0.5, 0.5, 0.0).normalize();
-const tmpQ = new THREE.Quaternion();
-const tmpM = new THREE.Matrix4();
-const currentM = new THREE.Matrix4();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x101010);
-
-    // geometry
-
-    const geometry = new THREE.InstancedBufferGeometry();
-
-    // per mesh data x,y,z,w,u,v,s,t for 4-element alignment
-    // only use x,y,z and u,v; but x, y, z, nx, ny, nz, u, v would be a good layout
-    const vertexBuffer = new THREE.InterleavedBuffer(
-        new Float32Array([
-            // Front
-            -1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 1, 0, 0, 1, -1, 1, 0, 1, 1, 0, 0,
-            // Back
-            1, 1, -1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0,
-            // Left
-            -1, 1, -1, 0, 1, 1, 0, 0, -1, 1, 1, 0, 1, 0, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0,
-            // Right
-            1, 1, 1, 0, 1, 0, 0, 0, 1, 1, -1, 0, 1, 1, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 1, 0, 0,
-            // Top
-            -1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 1, 0, 0, 1, 1, -1, 0, 1, 1, 0, 0,
-            // Bottom
-            1, -1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0, 0, -1, -1, -1, 0, 0, 1, 0, 0,
-        ]),
-        8,
-    );
-
-    // Use vertexBuffer, starting at offset 0, 3 items in position attribute
-    const positions = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0);
-    geometry.setAttribute('position', positions);
-    // Use vertexBuffer, starting at offset 4, 2 items in uv attribute
-    const uvs = new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 4);
-    geometry.setAttribute('uv', uvs);
-
-    const indices = new Uint16Array([
-        0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 8, 10, 9, 10, 11, 9, 12, 14, 13, 14, 15, 13, 16, 17, 18, 18, 17, 19, 20, 21,
-        22, 22, 21, 23,
-    ]);
-
-    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
-
-    // material
-
-    const material = new THREE.MeshBasicMaterial();
-    material.map = new THREE.TextureLoader().load('textures/crate.gif');
-    material.map.colorSpace = THREE.SRGBColorSpace;
-    material.map.flipY = false;
-
-    // per instance data
-
-    const matrix = new THREE.Matrix4();
-    const offset = new THREE.Vector3();
-    const orientation = new THREE.Quaternion();
-    const scale = new THREE.Vector3(1, 1, 1);
-    let x, y, z, w;
-
-    mesh = new THREE.InstancedMesh(geometry, material, instances);
-
-    for (let i = 0; i < instances; i++) {
-        // offsets
-
-        x = Math.random() * 100 - 50;
-        y = Math.random() * 100 - 50;
-        z = Math.random() * 100 - 50;
-
-        offset.set(x, y, z).normalize();
-        offset.multiplyScalar(5); // move out at least 5 units from center in current direction
-        offset.set(x + offset.x, y + offset.y, z + offset.z);
-
-        // orientations
-
-        x = Math.random() * 2 - 1;
-        y = Math.random() * 2 - 1;
-        z = Math.random() * 2 - 1;
-        w = Math.random() * 2 - 1;
-
-        orientation.set(x, y, z, w).normalize();
-
-        matrix.compose(offset, orientation, scale);
-
-        mesh.setMatrixAt(i, matrix);
-    }
-
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = performance.now();
-
-    mesh.rotation.y = time * 0.00005;
-
-    const delta = (time - lastTime) / 5000;
-    tmpQ.set(moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1).normalize();
-    tmpM.makeRotationFromQuaternion(tmpQ);
-
-    for (let i = 0, il = instances; i < il; i++) {
-        mesh.getMatrixAt(i, currentM);
-        currentM.multiply(tmpM);
-        mesh.setMatrixAt(i, currentM);
-    }
-
-    mesh.instanceMatrix.needsUpdate = true;
-    mesh.computeBoundingSphere();
-
-    lastTime = time;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_lines.ts b/examples-testing/examples/webgl_buffergeometry_lines.ts
deleted file mode 100644
index 1aaa5ca4a..000000000
--- a/examples-testing/examples/webgl_buffergeometry_lines.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats, clock;
-
-let camera, scene, renderer;
-
-let line;
-
-const segments = 10000;
-const r = 800;
-let t = 0;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 4000);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    const geometry = new THREE.BufferGeometry();
-    const material = new THREE.LineBasicMaterial({ vertexColors: true });
-
-    const positions = [];
-    const colors = [];
-
-    for (let i = 0; i < segments; i++) {
-        const x = Math.random() * r - r / 2;
-        const y = Math.random() * r - r / 2;
-        const z = Math.random() * r - r / 2;
-
-        // positions
-
-        positions.push(x, y, z);
-
-        // colors
-
-        colors.push(x / r + 0.5);
-        colors.push(y / r + 0.5);
-        colors.push(z / r + 0.5);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-    generateMorphTargets(geometry);
-
-    geometry.computeBoundingSphere();
-
-    line = new THREE.Line(geometry, material);
-    scene.add(line);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const delta = clock.getDelta();
-    const time = clock.getElapsedTime();
-
-    line.rotation.x = time * 0.25;
-    line.rotation.y = time * 0.5;
-
-    t += delta * 0.5;
-    line.morphTargetInfluences[0] = Math.abs(Math.sin(t));
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
-
-function generateMorphTargets(geometry) {
-    const data = [];
-
-    for (let i = 0; i < segments; i++) {
-        const x = Math.random() * r - r / 2;
-        const y = Math.random() * r - r / 2;
-        const z = Math.random() * r - r / 2;
-
-        data.push(x, y, z);
-    }
-
-    const morphTarget = new THREE.Float32BufferAttribute(data, 3);
-    morphTarget.name = 'target1';
-
-    geometry.morphAttributes.position = [morphTarget];
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts b/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
deleted file mode 100644
index 58296087e..000000000
--- a/examples-testing/examples/webgl_buffergeometry_lines_indexed.ts
+++ /dev/null
@@ -1,179 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let parent_node;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 9000;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.BufferGeometry();
-    const material = new THREE.LineBasicMaterial({ vertexColors: true });
-
-    const indices = [];
-    const positions = [];
-    const colors = [];
-
-    let next_positions_index = 0;
-
-    //
-
-    const iteration_count = 4;
-    const rangle = (60 * Math.PI) / 180.0;
-
-    function add_vertex(v) {
-        positions.push(v.x, v.y, v.z);
-        colors.push(Math.random() * 0.5 + 0.5, Math.random() * 0.5 + 0.5, 1);
-
-        return next_positions_index++;
-    }
-
-    // simple Koch curve
-
-    function snowflake_iteration(p0, p4, depth) {
-        if (--depth < 0) {
-            const i = next_positions_index - 1; // p0 already there
-            add_vertex(p4);
-            indices.push(i, i + 1);
-
-            return;
-        }
-
-        const v = p4.clone().sub(p0);
-        const v_tier = v.clone().multiplyScalar(1 / 3);
-        const p1 = p0.clone().add(v_tier);
-
-        const angle = Math.atan2(v.y, v.x) + rangle;
-        const length = v_tier.length();
-        const p2 = p1.clone();
-        p2.x += Math.cos(angle) * length;
-        p2.y += Math.sin(angle) * length;
-
-        const p3 = p0.clone().add(v_tier).add(v_tier);
-
-        snowflake_iteration(p0, p1, depth);
-        snowflake_iteration(p1, p2, depth);
-        snowflake_iteration(p2, p3, depth);
-        snowflake_iteration(p3, p4, depth);
-    }
-
-    function snowflake(points, loop, x_offset) {
-        for (let iteration = 0; iteration != iteration_count; iteration++) {
-            add_vertex(points[0]);
-
-            for (let p_index = 0, p_count = points.length - 1; p_index != p_count; p_index++) {
-                snowflake_iteration(points[p_index], points[p_index + 1], iteration);
-            }
-
-            if (loop) snowflake_iteration(points[points.length - 1], points[0], iteration);
-
-            // translate input curve for next iteration
-
-            for (let p_index = 0, p_count = points.length; p_index != p_count; p_index++) {
-                points[p_index].x += x_offset;
-            }
-        }
-    }
-
-    let y = 0;
-
-    snowflake([new THREE.Vector3(0, y, 0), new THREE.Vector3(500, y, 0)], false, 600);
-
-    y += 600;
-    snowflake(
-        [new THREE.Vector3(0, y, 0), new THREE.Vector3(250, y + 400, 0), new THREE.Vector3(500, y, 0)],
-        true,
-        600,
-    );
-
-    y += 600;
-    snowflake(
-        [
-            new THREE.Vector3(0, y, 0),
-            new THREE.Vector3(500, y, 0),
-            new THREE.Vector3(500, y + 500, 0),
-            new THREE.Vector3(0, y + 500, 0),
-        ],
-        true,
-        600,
-    );
-
-    y += 1000;
-    snowflake(
-        [
-            new THREE.Vector3(250, y, 0),
-            new THREE.Vector3(500, y, 0),
-            new THREE.Vector3(250, y, 0),
-            new THREE.Vector3(250, y + 250, 0),
-            new THREE.Vector3(250, y, 0),
-            new THREE.Vector3(0, y, 0),
-            new THREE.Vector3(250, y, 0),
-            new THREE.Vector3(250, y - 250, 0),
-            new THREE.Vector3(250, y, 0),
-        ],
-        false,
-        600,
-    );
-
-    //
-
-    geometry.setIndex(indices);
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-    geometry.computeBoundingSphere();
-
-    const lineSegments = new THREE.LineSegments(geometry, material);
-    lineSegments.position.x -= 1200;
-    lineSegments.position.y -= 1200;
-
-    parent_node = new THREE.Object3D();
-    parent_node.add(lineSegments);
-
-    scene.add(parent_node);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    parent_node.rotation.z = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_points.ts b/examples-testing/examples/webgl_buffergeometry_points.ts
deleted file mode 100644
index 4547d9d08..000000000
--- a/examples-testing/examples/webgl_buffergeometry_points.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let points;
-
-init();
-animate();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    const particles = 500000;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const colors = [];
-
-    const color = new THREE.Color();
-
-    const n = 1000,
-        n2 = n / 2; // particles spread in the cube
-
-    for (let i = 0; i < particles; i++) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        positions.push(x, y, z);
-
-        // colors
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
-
-        colors.push(color.r, color.g, color.b);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    geometry.computeBoundingSphere();
-
-    //
-
-    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
-
-    points = new THREE.Points(geometry, material);
-    scene.add(points);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    points.rotation.x = time * 0.25;
-    points.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts b/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
deleted file mode 100644
index 93eed992e..000000000
--- a/examples-testing/examples/webgl_buffergeometry_points_interleaved.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let points;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 5, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    const particles = 500000;
-
-    const geometry = new THREE.BufferGeometry();
-
-    // create a generic buffer of binary data (a single particle has 16 bytes of data)
-
-    const arrayBuffer = new ArrayBuffer(particles * 16);
-
-    // the following typed arrays share the same buffer
-
-    const interleavedFloat32Buffer = new Float32Array(arrayBuffer);
-    const interleavedUint8Buffer = new Uint8Array(arrayBuffer);
-
-    //
-
-    const color = new THREE.Color();
-
-    const n = 1000,
-        n2 = n / 2; // particles spread in the cube
-
-    for (let i = 0; i < interleavedFloat32Buffer.length; i += 4) {
-        // position (first 12 bytes)
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        interleavedFloat32Buffer[i + 0] = x;
-        interleavedFloat32Buffer[i + 1] = y;
-        interleavedFloat32Buffer[i + 2] = z;
-
-        // color (last 4 bytes)
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz, THREE.SRGBColorSpace);
-
-        const j = (i + 3) * 4;
-
-        interleavedUint8Buffer[j + 0] = color.r * 255;
-        interleavedUint8Buffer[j + 1] = color.g * 255;
-        interleavedUint8Buffer[j + 2] = color.b * 255;
-        interleavedUint8Buffer[j + 3] = 0; // not needed
-    }
-
-    const interleavedBuffer32 = new THREE.InterleavedBuffer(interleavedFloat32Buffer, 4);
-    const interleavedBuffer8 = new THREE.InterleavedBuffer(interleavedUint8Buffer, 16);
-
-    geometry.setAttribute('position', new THREE.InterleavedBufferAttribute(interleavedBuffer32, 3, 0, false));
-    geometry.setAttribute('color', new THREE.InterleavedBufferAttribute(interleavedBuffer8, 3, 12, true));
-
-    //
-
-    const material = new THREE.PointsMaterial({ size: 15, vertexColors: true });
-
-    points = new THREE.Points(geometry, material);
-    scene.add(points);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    points.rotation.x = time * 0.25;
-    points.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_rawshader.ts b/examples-testing/examples/webgl_buffergeometry_rawshader.ts
deleted file mode 100644
index 5bc113dc3..000000000
--- a/examples-testing/examples/webgl_buffergeometry_rawshader.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x101010);
-
-    // geometry
-    // nr of triangles with 3 vertices per triangle
-    const vertexCount = 200 * 3;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const colors = [];
-
-    for (let i = 0; i < vertexCount; i++) {
-        // adding x,y,z
-        positions.push(Math.random() - 0.5);
-        positions.push(Math.random() - 0.5);
-        positions.push(Math.random() - 0.5);
-
-        // adding r,g,b,a
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-    }
-
-    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
-    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 4);
-
-    colorAttribute.normalized = true; // this will map the buffer values to 0.0f - +1.0f in the shader
-
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('color', colorAttribute);
-
-    // material
-
-    const material = new THREE.RawShaderMaterial({
-        uniforms: {
-            time: { value: 1.0 },
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        side: THREE.DoubleSide,
-        transparent: true,
-    });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = performance.now();
-
-    const object = scene.children[0];
-
-    object.rotation.y = time * 0.0005;
-    object.material.uniforms.time.value = time * 0.005;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_selective_draw.ts b/examples-testing/examples/webgl_buffergeometry_selective_draw.ts
deleted file mode 100644
index d07176c51..000000000
--- a/examples-testing/examples/webgl_buffergeometry_selective_draw.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-let geometry, mesh;
-const numLat = 100;
-const numLng = 200;
-let numLinesCulled = 0;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.z = 3.5;
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    addLines(1.0);
-
-    const hideLinesButton = document.getElementById('hideLines');
-    hideLinesButton.addEventListener('click', hideLines);
-
-    const showAllLinesButton = document.getElementById('showAllLines');
-    showAllLinesButton.addEventListener('click', showAllLines);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-}
-
-function addLines(radius) {
-    geometry = new THREE.BufferGeometry();
-    const linePositions = new Float32Array(numLat * numLng * 3 * 2);
-    const lineColors = new Float32Array(numLat * numLng * 3 * 2);
-    const visible = new Float32Array(numLat * numLng * 2);
-
-    for (let i = 0; i < numLat; ++i) {
-        for (let j = 0; j < numLng; ++j) {
-            const lat = (Math.random() * Math.PI) / 50.0 + (i / numLat) * Math.PI;
-            const lng = (Math.random() * Math.PI) / 50.0 + (j / numLng) * 2 * Math.PI;
-
-            const index = i * numLng + j;
-
-            linePositions[index * 6 + 0] = 0;
-            linePositions[index * 6 + 1] = 0;
-            linePositions[index * 6 + 2] = 0;
-            linePositions[index * 6 + 3] = radius * Math.sin(lat) * Math.cos(lng);
-            linePositions[index * 6 + 4] = radius * Math.cos(lat);
-            linePositions[index * 6 + 5] = radius * Math.sin(lat) * Math.sin(lng);
-
-            const color = new THREE.Color(0xffffff);
-
-            color.setHSL(lat / Math.PI, 1.0, 0.2);
-            lineColors[index * 6 + 0] = color.r;
-            lineColors[index * 6 + 1] = color.g;
-            lineColors[index * 6 + 2] = color.b;
-
-            color.setHSL(lat / Math.PI, 1.0, 0.7);
-            lineColors[index * 6 + 3] = color.r;
-            lineColors[index * 6 + 4] = color.g;
-            lineColors[index * 6 + 5] = color.b;
-
-            // non-0 is visible
-            visible[index * 2 + 0] = 1.0;
-            visible[index * 2 + 1] = 1.0;
-        }
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
-    geometry.setAttribute('vertColor', new THREE.BufferAttribute(lineColors, 3));
-    geometry.setAttribute('visible', new THREE.BufferAttribute(visible, 1));
-
-    geometry.computeBoundingSphere();
-
-    const shaderMaterial = new THREE.ShaderMaterial({
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    mesh = new THREE.LineSegments(geometry, shaderMaterial);
-    scene.add(mesh);
-
-    updateCount();
-}
-
-function updateCount() {
-    const str =
-        '1 draw call, ' +
-        numLat * numLng +
-        ' lines, ' +
-        numLinesCulled +
-        ' culled (<a target="_blank" href="http://callum.com">author</a>)';
-    document.getElementById('title').innerHTML = str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
-}
-
-function hideLines() {
-    for (let i = 0; i < geometry.attributes.visible.array.length; i += 2) {
-        if (Math.random() > 0.75) {
-            if (geometry.attributes.visible.array[i + 0]) {
-                ++numLinesCulled;
-            }
-
-            geometry.attributes.visible.array[i + 0] = 0;
-            geometry.attributes.visible.array[i + 1] = 0;
-        }
-    }
-
-    geometry.attributes.visible.needsUpdate = true;
-
-    updateCount();
-}
-
-function showAllLines() {
-    numLinesCulled = 0;
-
-    for (let i = 0; i < geometry.attributes.visible.array.length; i += 2) {
-        geometry.attributes.visible.array[i + 0] = 1;
-        geometry.attributes.visible.array[i + 1] = 1;
-    }
-
-    geometry.attributes.visible.needsUpdate = true;
-
-    updateCount();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.25;
-    mesh.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_buffergeometry_uint.ts b/examples-testing/examples/webgl_buffergeometry_uint.ts
deleted file mode 100644
index 0b8df6ec7..000000000
--- a/examples-testing/examples/webgl_buffergeometry_uint.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let mesh;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
-    light1.position.set(1, 1, 1);
-    scene.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
-    light2.position.set(0, -1, 0);
-    scene.add(light2);
-
-    //
-
-    const triangles = 500000;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const normals = [];
-    const colors = [];
-
-    const color = new THREE.Color();
-
-    const n = 800,
-        n2 = n / 2; // triangles spread in the cube
-    const d = 12,
-        d2 = d / 2; // individual triangle size
-
-    const pA = new THREE.Vector3();
-    const pB = new THREE.Vector3();
-    const pC = new THREE.Vector3();
-
-    const cb = new THREE.Vector3();
-    const ab = new THREE.Vector3();
-
-    for (let i = 0; i < triangles; i++) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        const ax = x + Math.random() * d - d2;
-        const ay = y + Math.random() * d - d2;
-        const az = z + Math.random() * d - d2;
-
-        const bx = x + Math.random() * d - d2;
-        const by = y + Math.random() * d - d2;
-        const bz = z + Math.random() * d - d2;
-
-        const cx = x + Math.random() * d - d2;
-        const cy = y + Math.random() * d - d2;
-        const cz = z + Math.random() * d - d2;
-
-        positions.push(ax, ay, az);
-        positions.push(bx, by, bz);
-        positions.push(cx, cy, cz);
-
-        // flat face normals
-
-        pA.set(ax, ay, az);
-        pB.set(bx, by, bz);
-        pC.set(cx, cy, cz);
-
-        cb.subVectors(pC, pB);
-        ab.subVectors(pA, pB);
-        cb.cross(ab);
-
-        cb.normalize();
-
-        const nx = cb.x;
-        const ny = cb.y;
-        const nz = cb.z;
-
-        normals.push(nx * 32767, ny * 32767, nz * 32767);
-        normals.push(nx * 32767, ny * 32767, nz * 32767);
-        normals.push(nx * 32767, ny * 32767, nz * 32767);
-
-        // colors
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz);
-
-        colors.push(color.r * 255, color.g * 255, color.b * 255);
-        colors.push(color.r * 255, color.g * 255, color.b * 255);
-        colors.push(color.r * 255, color.g * 255, color.b * 255);
-    }
-
-    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
-    const normalAttribute = new THREE.Int16BufferAttribute(normals, 3);
-    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 3);
-
-    normalAttribute.normalized = true; // this will map the buffer values to 0.0f - +1.0f in the shader
-    colorAttribute.normalized = true;
-
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('normal', normalAttribute);
-    geometry.setAttribute('color', colorAttribute);
-
-    geometry.computeBoundingSphere();
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xd5d5d5,
-        specular: 0xffffff,
-        shininess: 250,
-        side: THREE.DoubleSide,
-        vertexColors: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.25;
-    mesh.rotation.y = time * 0.5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_camera.ts b/examples-testing/examples/webgl_camera.ts
deleted file mode 100644
index f3d663603..000000000
--- a/examples-testing/examples/webgl_camera.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-let aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-
-let container, stats;
-let camera, scene, renderer, mesh;
-let cameraRig, activeCamera, activeHelper;
-let cameraPerspective, cameraOrtho;
-let cameraPerspectiveHelper, cameraOrthoHelper;
-const frustumSize = 600;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, 0.5 * aspect, 1, 10000);
-    camera.position.z = 2500;
-
-    cameraPerspective = new THREE.PerspectiveCamera(50, 0.5 * aspect, 150, 1000);
-
-    cameraPerspectiveHelper = new THREE.CameraHelper(cameraPerspective);
-    scene.add(cameraPerspectiveHelper);
-
-    //
-    cameraOrtho = new THREE.OrthographicCamera(
-        (0.5 * frustumSize * aspect) / -2,
-        (0.5 * frustumSize * aspect) / 2,
-        frustumSize / 2,
-        frustumSize / -2,
-        150,
-        1000,
-    );
-
-    cameraOrthoHelper = new THREE.CameraHelper(cameraOrtho);
-    scene.add(cameraOrthoHelper);
-
-    //
-
-    activeCamera = cameraPerspective;
-    activeHelper = cameraPerspectiveHelper;
-
-    // counteract different front orientation of cameras vs rig
-
-    cameraOrtho.rotation.y = Math.PI;
-    cameraPerspective.rotation.y = Math.PI;
-
-    cameraRig = new THREE.Group();
-
-    cameraRig.add(cameraPerspective);
-    cameraRig.add(cameraOrtho);
-
-    scene.add(cameraRig);
-
-    //
-
-    mesh = new THREE.Mesh(
-        new THREE.SphereGeometry(100, 16, 8),
-        new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }),
-    );
-    scene.add(mesh);
-
-    const mesh2 = new THREE.Mesh(
-        new THREE.SphereGeometry(50, 16, 8),
-        new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }),
-    );
-    mesh2.position.y = 150;
-    mesh.add(mesh2);
-
-    const mesh3 = new THREE.Mesh(
-        new THREE.SphereGeometry(5, 16, 8),
-        new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true }),
-    );
-    mesh3.position.z = 150;
-    cameraRig.add(mesh3);
-
-    //
-
-    const geometry = new THREE.BufferGeometry();
-    const vertices = [];
-
-    for (let i = 0; i < 10000; i++) {
-        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // x
-        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // y
-        vertices.push(THREE.MathUtils.randFloatSpread(2000)); // z
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    const particles = new THREE.Points(geometry, new THREE.PointsMaterial({ color: 0x888888 }));
-    scene.add(particles);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    renderer.setScissorTest(true);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('keydown', onKeyDown);
-}
-
-//
-
-function onKeyDown(event) {
-    switch (event.keyCode) {
-        case 79 /*O*/:
-            activeCamera = cameraOrtho;
-            activeHelper = cameraOrthoHelper;
-
-            break;
-
-        case 80 /*P*/:
-            activeCamera = cameraPerspective;
-            activeHelper = cameraPerspectiveHelper;
-
-            break;
-    }
-}
-
-//
-
-function onWindowResize() {
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-    aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    camera.aspect = 0.5 * aspect;
-    camera.updateProjectionMatrix();
-
-    cameraPerspective.aspect = 0.5 * aspect;
-    cameraPerspective.updateProjectionMatrix();
-
-    cameraOrtho.left = (-0.5 * frustumSize * aspect) / 2;
-    cameraOrtho.right = (0.5 * frustumSize * aspect) / 2;
-    cameraOrtho.top = frustumSize / 2;
-    cameraOrtho.bottom = -frustumSize / 2;
-    cameraOrtho.updateProjectionMatrix();
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const r = Date.now() * 0.0005;
-
-    mesh.position.x = 700 * Math.cos(r);
-    mesh.position.z = 700 * Math.sin(r);
-    mesh.position.y = 700 * Math.sin(r);
-
-    mesh.children[0].position.x = 70 * Math.cos(2 * r);
-    mesh.children[0].position.z = 70 * Math.sin(r);
-
-    if (activeCamera === cameraPerspective) {
-        cameraPerspective.fov = 35 + 30 * Math.sin(0.5 * r);
-        cameraPerspective.far = mesh.position.length();
-        cameraPerspective.updateProjectionMatrix();
-
-        cameraPerspectiveHelper.update();
-        cameraPerspectiveHelper.visible = true;
-
-        cameraOrthoHelper.visible = false;
-    } else {
-        cameraOrtho.far = mesh.position.length();
-        cameraOrtho.updateProjectionMatrix();
-
-        cameraOrthoHelper.update();
-        cameraOrthoHelper.visible = true;
-
-        cameraPerspectiveHelper.visible = false;
-    }
-
-    cameraRig.lookAt(mesh.position);
-
-    //
-
-    activeHelper.visible = false;
-
-    renderer.setClearColor(0x000000, 1);
-    renderer.setScissor(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.setViewport(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.render(scene, activeCamera);
-
-    //
-
-    activeHelper.visible = true;
-
-    renderer.setClearColor(0x111111, 1);
-    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.setViewport(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_camera_array.ts b/examples-testing/examples/webgl_camera_array.ts
deleted file mode 100644
index 8b10e27cb..000000000
--- a/examples-testing/examples/webgl_camera_array.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let mesh;
-const AMOUNT = 6;
-
-init();
-
-function init() {
-    const ASPECT_RATIO = window.innerWidth / window.innerHeight;
-
-    const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
-    const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
-
-    const cameras = [];
-
-    for (let y = 0; y < AMOUNT; y++) {
-        for (let x = 0; x < AMOUNT; x++) {
-            const subcamera = new THREE.PerspectiveCamera(40, ASPECT_RATIO, 0.1, 10);
-            subcamera.viewport = new THREE.Vector4(
-                Math.floor(x * WIDTH),
-                Math.floor(y * HEIGHT),
-                Math.ceil(WIDTH),
-                Math.ceil(HEIGHT),
-            );
-            subcamera.position.x = x / AMOUNT - 0.5;
-            subcamera.position.y = 0.5 - y / AMOUNT;
-            subcamera.position.z = 1.5;
-            subcamera.position.multiplyScalar(2);
-            subcamera.lookAt(0, 0, 0);
-            subcamera.updateMatrixWorld();
-            cameras.push(subcamera);
-        }
-    }
-
-    camera = new THREE.ArrayCamera(cameras);
-    camera.position.z = 3;
-
-    scene = new THREE.Scene();
-
-    scene.add(new THREE.AmbientLight(0x999999));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0.5, 0.5, 1);
-    light.castShadow = true;
-    light.shadow.camera.zoom = 4; // tighter shadow map
-    scene.add(light);
-
-    const geometryBackground = new THREE.PlaneGeometry(100, 100);
-    const materialBackground = new THREE.MeshPhongMaterial({ color: 0x000066 });
-
-    const background = new THREE.Mesh(geometryBackground, materialBackground);
-    background.receiveShadow = true;
-    background.position.set(0, 0, -1);
-    scene.add(background);
-
-    const geometryCylinder = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
-    const materialCylinder = new THREE.MeshPhongMaterial({ color: 0xff0000 });
-
-    mesh = new THREE.Mesh(geometryCylinder, materialCylinder);
-    mesh.castShadow = true;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const ASPECT_RATIO = window.innerWidth / window.innerHeight;
-    const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
-    const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
-
-    camera.aspect = ASPECT_RATIO;
-    camera.updateProjectionMatrix();
-
-    for (let y = 0; y < AMOUNT; y++) {
-        for (let x = 0; x < AMOUNT; x++) {
-            const subcamera = camera.cameras[AMOUNT * y + x];
-
-            subcamera.viewport.set(Math.floor(x * WIDTH), Math.floor(y * HEIGHT), Math.ceil(WIDTH), Math.ceil(HEIGHT));
-
-            subcamera.aspect = ASPECT_RATIO;
-            subcamera.updateProjectionMatrix();
-        }
-    }
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.rotation.x += 0.005;
-    mesh.rotation.z += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts b/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
deleted file mode 100644
index f1d440004..000000000
--- a/examples-testing/examples/webgl_camera_logarithmicdepthbuffer.ts
+++ /dev/null
@@ -1,248 +0,0 @@
-import * as THREE from 'three';
-
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-// 1 micrometer to 100 billion light years in one scene, with 1 unit = 1 meter?  preposterous!  and yet...
-const NEAR = 1e-6,
-    FAR = 1e27;
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-let screensplit = 0.25,
-    screensplit_right = 0;
-const mouse = [0.5, 0.5];
-let zoompos = -100,
-    minzoomspeed = 0.015;
-let zoomspeed = minzoomspeed;
-
-let container, border, stats;
-const objects = {};
-
-// Generate a number of text labels, from 1µm in size up to 100,000,000 light years
-// Try to use some descriptive real-world examples of objects at each scale
-
-const labeldata = [
-    { size: 0.01, scale: 0.0001, label: 'microscopic (1µm)' }, // FIXME - triangulating text fails at this size, so we scale instead
-    { size: 0.01, scale: 0.1, label: 'minuscule (1mm)' },
-    { size: 0.01, scale: 1.0, label: 'tiny (1cm)' },
-    { size: 1, scale: 1.0, label: 'child-sized (1m)' },
-    { size: 10, scale: 1.0, label: 'tree-sized (10m)' },
-    { size: 100, scale: 1.0, label: 'building-sized (100m)' },
-    { size: 1000, scale: 1.0, label: 'medium (1km)' },
-    { size: 10000, scale: 1.0, label: 'city-sized (10km)' },
-    { size: 3400000, scale: 1.0, label: 'moon-sized (3,400 Km)' },
-    { size: 12000000, scale: 1.0, label: 'planet-sized (12,000 km)' },
-    { size: 1400000000, scale: 1.0, label: 'sun-sized (1,400,000 km)' },
-    { size: 7.47e12, scale: 1.0, label: 'solar system-sized (50Au)' },
-    { size: 9.4605284e15, scale: 1.0, label: 'gargantuan (1 light year)' },
-    { size: 3.08567758e16, scale: 1.0, label: 'ludicrous (1 parsec)' },
-    { size: 1e19, scale: 1.0, label: 'mind boggling (1000 light years)' },
-];
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    const loader = new FontLoader();
-    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
-        const scene = initScene(font);
-
-        // Initialize two copies of the same scene, one with normal z-buffer and one with logarithmic z-buffer
-        objects.normal = initView(scene, 'normal', false);
-        objects.logzbuf = initView(scene, 'logzbuf', true);
-
-        animate();
-    });
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
-    border = document.getElementById('renderer_border');
-    border.addEventListener('pointerdown', onBorderPointerDown);
-
-    window.addEventListener('mousemove', onMouseMove);
-    window.addEventListener('resize', onWindowResize);
-    window.addEventListener('wheel', onMouseWheel);
-}
-
-function initView(scene, name, logDepthBuf) {
-    const framecontainer = document.getElementById('container_' + name);
-
-    const camera = new THREE.PerspectiveCamera(50, (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT, NEAR, FAR);
-    scene.add(camera);
-
-    const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: logDepthBuf });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.domElement.style.position = 'relative';
-    renderer.domElement.id = 'renderer_' + name;
-    framecontainer.appendChild(renderer.domElement);
-
-    return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
-}
-
-function initScene(font) {
-    const scene = new THREE.Scene();
-
-    scene.add(new THREE.AmbientLight(0x777777));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(100, 100, 100);
-    scene.add(light);
-
-    const materialargs = {
-        color: 0xffffff,
-        specular: 0x050505,
-        shininess: 50,
-        emissive: 0x000000,
-    };
-
-    const geometry = new THREE.SphereGeometry(0.5, 24, 12);
-
-    for (let i = 0; i < labeldata.length; i++) {
-        const scale = labeldata[i].scale || 1;
-
-        const labelgeo = new TextGeometry(labeldata[i].label, {
-            font: font,
-            size: labeldata[i].size,
-            depth: labeldata[i].size / 2,
-        });
-
-        labelgeo.computeBoundingSphere();
-
-        // center text
-        labelgeo.translate(-labelgeo.boundingSphere.radius, 0, 0);
-
-        materialargs.color = new THREE.Color().setHSL(Math.random(), 0.5, 0.5);
-
-        const material = new THREE.MeshPhongMaterial(materialargs);
-
-        const group = new THREE.Group();
-        group.position.z = -labeldata[i].size * scale;
-        scene.add(group);
-
-        const textmesh = new THREE.Mesh(labelgeo, material);
-        textmesh.scale.set(scale, scale, scale);
-        textmesh.position.z = -labeldata[i].size * scale;
-        textmesh.position.y = (labeldata[i].size / 4) * scale;
-        group.add(textmesh);
-
-        const dotmesh = new THREE.Mesh(geometry, material);
-        dotmesh.position.y = (-labeldata[i].size / 4) * scale;
-        dotmesh.scale.multiplyScalar(labeldata[i].size * scale);
-        group.add(dotmesh);
-    }
-
-    return scene;
-}
-
-function updateRendererSizes() {
-    // Recalculate size for both renderers when screen size or split location changes
-
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    screensplit_right = 1 - screensplit;
-
-    objects.normal.renderer.setSize(screensplit * SCREEN_WIDTH, SCREEN_HEIGHT);
-    objects.normal.camera.aspect = (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT;
-    objects.normal.camera.updateProjectionMatrix();
-    objects.normal.camera.setViewOffset(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH * screensplit, SCREEN_HEIGHT);
-    objects.normal.container.style.width = screensplit * 100 + '%';
-
-    objects.logzbuf.renderer.setSize(screensplit_right * SCREEN_WIDTH, SCREEN_HEIGHT);
-    objects.logzbuf.camera.aspect = (screensplit_right * SCREEN_WIDTH) / SCREEN_HEIGHT;
-    objects.logzbuf.camera.updateProjectionMatrix();
-    objects.logzbuf.camera.setViewOffset(
-        SCREEN_WIDTH,
-        SCREEN_HEIGHT,
-        SCREEN_WIDTH * screensplit,
-        0,
-        SCREEN_WIDTH * screensplit_right,
-        SCREEN_HEIGHT,
-    );
-    objects.logzbuf.container.style.width = screensplit_right * 100 + '%';
-
-    border.style.left = screensplit * 100 + '%';
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-    render();
-}
-
-function render() {
-    // Put some limits on zooming
-    const minzoom = labeldata[0].size * labeldata[0].scale * 1;
-    const maxzoom = labeldata[labeldata.length - 1].size * labeldata[labeldata.length - 1].scale * 100;
-    let damping = Math.abs(zoomspeed) > minzoomspeed ? 0.95 : 1.0;
-
-    // Zoom out faster the further out you go
-    const zoom = THREE.MathUtils.clamp(Math.pow(Math.E, zoompos), minzoom, maxzoom);
-    zoompos = Math.log(zoom);
-
-    // Slow down quickly at the zoom limits
-    if ((zoom == minzoom && zoomspeed < 0) || (zoom == maxzoom && zoomspeed > 0)) {
-        damping = 0.85;
-    }
-
-    zoompos += zoomspeed;
-    zoomspeed *= damping;
-
-    objects.normal.camera.position.x = Math.sin(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
-    objects.normal.camera.position.y = Math.sin(0.25 * Math.PI * (mouse[1] - 0.5)) * zoom;
-    objects.normal.camera.position.z = Math.cos(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
-    objects.normal.camera.lookAt(objects.normal.scene.position);
-
-    // Clone camera settings across both scenes
-    objects.logzbuf.camera.position.copy(objects.normal.camera.position);
-    objects.logzbuf.camera.quaternion.copy(objects.normal.camera.quaternion);
-
-    // Update renderer sizes if the split has changed
-    if (screensplit_right != 1 - screensplit) {
-        updateRendererSizes();
-    }
-
-    objects.normal.renderer.render(objects.normal.scene, objects.normal.camera);
-    objects.logzbuf.renderer.render(objects.logzbuf.scene, objects.logzbuf.camera);
-
-    stats.update();
-}
-
-function onWindowResize() {
-    updateRendererSizes();
-}
-
-function onBorderPointerDown() {
-    // activate draggable window resizing bar
-    window.addEventListener('pointermove', onBorderPointerMove);
-    window.addEventListener('pointerup', onBorderPointerUp);
-}
-
-function onBorderPointerMove(ev) {
-    screensplit = Math.max(0, Math.min(1, ev.clientX / window.innerWidth));
-}
-
-function onBorderPointerUp() {
-    window.removeEventListener('pointermove', onBorderPointerMove);
-    window.removeEventListener('pointerup', onBorderPointerUp);
-}
-
-function onMouseMove(ev) {
-    mouse[0] = ev.clientX / window.innerWidth;
-    mouse[1] = ev.clientY / window.innerHeight;
-}
-
-function onMouseWheel(ev) {
-    const amount = ev.deltaY;
-    if (amount === 0) return;
-    const dir = amount / Math.abs(amount);
-    zoomspeed = dir / 10;
-
-    // Slow down default zoom speed after user starts zooming, to give them more control
-    minzoomspeed = 0.001;
-}
diff --git a/examples-testing/examples/webgl_clipculldistance.ts b/examples-testing/examples/webgl_clipculldistance.ts
deleted file mode 100644
index a5fb54d47..000000000
--- a/examples-testing/examples/webgl_clipculldistance.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, controls, clock, scene, renderer, stats;
-
-let material;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    if (renderer.extensions.has('WEBGL_clip_cull_distance') === false) {
-        document.getElementById('notSupported').style.display = '';
-        return;
-    }
-
-    const ext = renderer.getContext().getExtension('WEBGL_clip_cull_distance');
-    const gl = renderer.getContext();
-
-    gl.enable(ext.CLIP_DISTANCE0_WEBGL);
-
-    // geometry
-
-    const vertexCount = 200 * 3;
-
-    const geometry = new THREE.BufferGeometry();
-
-    const positions = [];
-    const colors = [];
-
-    for (let i = 0; i < vertexCount; i++) {
-        // adding x,y,z
-        positions.push(Math.random() - 0.5);
-        positions.push(Math.random() - 0.5);
-        positions.push(Math.random() - 0.5);
-
-        // adding r,g,b,a
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-        colors.push(Math.random() * 255);
-    }
-
-    const positionAttribute = new THREE.Float32BufferAttribute(positions, 3);
-    const colorAttribute = new THREE.Uint8BufferAttribute(colors, 4);
-    colorAttribute.normalized = true;
-
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('color', colorAttribute);
-
-    // material
-
-    material = new THREE.ShaderMaterial({
-        uniforms: {
-            time: { value: 1.0 },
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        side: THREE.DoubleSide,
-        transparent: true,
-        vertexColors: true,
-    });
-
-    material.extensions.clipCullDistance = true;
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-    stats.update();
-
-    material.uniforms.time.value = clock.getElapsedTime();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_clipping.ts b/examples-testing/examples/webgl_clipping.ts
deleted file mode 100644
index cde10c7d1..000000000
--- a/examples-testing/examples/webgl_clipping.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, startTime, object, stats;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
-
-    camera.position.set(0, 1.3, 3);
-
-    scene = new THREE.Scene();
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const spotLight = new THREE.SpotLight(0xffffff, 60);
-    spotLight.angle = Math.PI / 5;
-    spotLight.penumbra = 0.2;
-    spotLight.position.set(2, 3, 3);
-    spotLight.castShadow = true;
-    spotLight.shadow.camera.near = 3;
-    spotLight.shadow.camera.far = 10;
-    spotLight.shadow.mapSize.width = 1024;
-    spotLight.shadow.mapSize.height = 1024;
-    scene.add(spotLight);
-
-    const dirLight = new THREE.DirectionalLight(0x55505a, 3);
-    dirLight.position.set(0, 3, 0);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.near = 1;
-    dirLight.shadow.camera.far = 10;
-
-    dirLight.shadow.camera.right = 1;
-    dirLight.shadow.camera.left = -1;
-    dirLight.shadow.camera.top = 1;
-    dirLight.shadow.camera.bottom = -1;
-
-    dirLight.shadow.mapSize.width = 1024;
-    dirLight.shadow.mapSize.height = 1024;
-    scene.add(dirLight);
-
-    // ***** Clipping planes: *****
-
-    const localPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.8);
-    const globalPlane = new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0.1);
-
-    // Geometry
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0x80ee10,
-        shininess: 100,
-        side: THREE.DoubleSide,
-
-        // ***** Clipping setup (material): *****
-        clippingPlanes: [localPlane],
-        clipShadows: true,
-
-        alphaToCoverage: true,
-    });
-
-    const geometry = new THREE.TorusKnotGeometry(0.4, 0.08, 95, 20);
-
-    object = new THREE.Mesh(geometry, material);
-    object.castShadow = true;
-    scene.add(object);
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(9, 9, 1, 1),
-        new THREE.MeshPhongMaterial({ color: 0xa0adaf, shininess: 150 }),
-    );
-
-    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // Renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // ***** Clipping setup (renderer): *****
-    const globalPlanes = [globalPlane],
-        Empty = Object.freeze([]);
-    renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
-    renderer.localClippingEnabled = true;
-
-    // Stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // Controls
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 1, 0);
-    controls.update();
-
-    // GUI
-
-    const gui = new GUI(),
-        props = {
-            alphaToCoverage: true,
-        },
-        folderLocal = gui.addFolder('Local Clipping'),
-        propsLocal = {
-            get Enabled() {
-                return renderer.localClippingEnabled;
-            },
-            set Enabled(v) {
-                renderer.localClippingEnabled = v;
-            },
-
-            get Shadows() {
-                return material.clipShadows;
-            },
-            set Shadows(v) {
-                material.clipShadows = v;
-            },
-
-            get Plane() {
-                return localPlane.constant;
-            },
-            set Plane(v) {
-                localPlane.constant = v;
-            },
-        },
-        folderGlobal = gui.addFolder('Global Clipping'),
-        propsGlobal = {
-            get Enabled() {
-                return renderer.clippingPlanes !== Empty;
-            },
-            set Enabled(v) {
-                renderer.clippingPlanes = v ? globalPlanes : Empty;
-            },
-
-            get Plane() {
-                return globalPlane.constant;
-            },
-            set Plane(v) {
-                globalPlane.constant = v;
-            },
-        };
-
-    gui.add(props, 'alphaToCoverage').onChange(function (value) {
-        ground.material.alphaToCoverage = value;
-        ground.material.needsUpdate = true;
-
-        material.alphaToCoverage = value;
-        material.needsUpdate = true;
-    });
-    folderLocal.add(propsLocal, 'Enabled');
-    folderLocal.add(propsLocal, 'Shadows');
-    folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);
-
-    folderGlobal.add(propsGlobal, 'Enabled');
-    folderGlobal.add(propsGlobal, 'Plane', -0.4, 3);
-
-    // Start
-
-    startTime = Date.now();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const currentTime = Date.now();
-    const time = (currentTime - startTime) / 1000;
-
-    object.position.y = 0.8;
-    object.rotation.x = time * 0.5;
-    object.rotation.y = time * 0.2;
-    object.scale.setScalar(Math.cos(time) * 0.125 + 0.875);
-
-    stats.begin();
-    renderer.render(scene, camera);
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_clipping_advanced.ts b/examples-testing/examples/webgl_clipping_advanced.ts
deleted file mode 100644
index 614d710da..000000000
--- a/examples-testing/examples/webgl_clipping_advanced.ts
+++ /dev/null
@@ -1,355 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-function planesFromMesh(vertices, indices) {
-    // creates a clipping volume from a convex triangular mesh
-    // specified by the arrays 'vertices' and 'indices'
-
-    const n = indices.length / 3,
-        result = new Array(n);
-
-    for (let i = 0, j = 0; i < n; ++i, j += 3) {
-        const a = vertices[indices[j]],
-            b = vertices[indices[j + 1]],
-            c = vertices[indices[j + 2]];
-
-        result[i] = new THREE.Plane().setFromCoplanarPoints(a, b, c);
-    }
-
-    return result;
-}
-
-function createPlanes(n) {
-    // creates an array of n uninitialized plane objects
-
-    const result = new Array(n);
-
-    for (let i = 0; i !== n; ++i) result[i] = new THREE.Plane();
-
-    return result;
-}
-
-function assignTransformedPlanes(planesOut, planesIn, matrix) {
-    // sets an array of existing planes to transformed 'planesIn'
-
-    for (let i = 0, n = planesIn.length; i !== n; ++i) planesOut[i].copy(planesIn[i]).applyMatrix4(matrix);
-}
-
-function cylindricalPlanes(n, innerRadius) {
-    const result = createPlanes(n);
-
-    for (let i = 0; i !== n; ++i) {
-        const plane = result[i],
-            angle = (i * Math.PI * 2) / n;
-
-        plane.normal.set(Math.cos(angle), 0, Math.sin(angle));
-
-        plane.constant = innerRadius;
-    }
-
-    return result;
-}
-
-const planeToMatrix = (function () {
-    // creates a matrix that aligns X/Y to a given plane
-
-    // temporaries:
-    const xAxis = new THREE.Vector3(),
-        yAxis = new THREE.Vector3(),
-        trans = new THREE.Vector3();
-
-    return function planeToMatrix(plane) {
-        const zAxis = plane.normal,
-            matrix = new THREE.Matrix4();
-
-        // Hughes & Moeller '99
-        // "Building an Orthonormal Basis from a Unit Vector."
-
-        if (Math.abs(zAxis.x) > Math.abs(zAxis.z)) {
-            yAxis.set(-zAxis.y, zAxis.x, 0);
-        } else {
-            yAxis.set(0, -zAxis.z, zAxis.y);
-        }
-
-        xAxis.crossVectors(yAxis.normalize(), zAxis);
-
-        plane.coplanarPoint(trans);
-        return matrix.set(
-            xAxis.x,
-            yAxis.x,
-            zAxis.x,
-            trans.x,
-            xAxis.y,
-            yAxis.y,
-            zAxis.y,
-            trans.y,
-            xAxis.z,
-            yAxis.z,
-            zAxis.z,
-            trans.z,
-            0,
-            0,
-            0,
-            1,
-        );
-    };
-})();
-
-// A regular tetrahedron for the clipping volume:
-
-const Vertices = [
-        new THREE.Vector3(+1, 0, +Math.SQRT1_2),
-        new THREE.Vector3(-1, 0, +Math.SQRT1_2),
-        new THREE.Vector3(0, +1, -Math.SQRT1_2),
-        new THREE.Vector3(0, -1, -Math.SQRT1_2),
-    ],
-    Indices = [0, 1, 2, 0, 2, 3, 0, 3, 1, 1, 3, 2],
-    Planes = planesFromMesh(Vertices, Indices),
-    PlaneMatrices = Planes.map(planeToMatrix),
-    GlobalClippingPlanes = cylindricalPlanes(5, 2.5),
-    Empty = Object.freeze([]);
-
-let camera, scene, renderer, startTime, stats, object, clipMaterial, volumeVisualization, globalClippingPlanes;
-
-function init() {
-    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
-
-    camera.position.set(0, 1.5, 3);
-
-    scene = new THREE.Scene();
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0xffffff));
-
-    const spotLight = new THREE.SpotLight(0xffffff, 60);
-    spotLight.angle = Math.PI / 5;
-    spotLight.penumbra = 0.2;
-    spotLight.position.set(2, 3, 3);
-    spotLight.castShadow = true;
-    spotLight.shadow.camera.near = 3;
-    spotLight.shadow.camera.far = 10;
-    spotLight.shadow.mapSize.width = 1024;
-    spotLight.shadow.mapSize.height = 1024;
-    scene.add(spotLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
-    dirLight.position.set(0, 2, 0);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.near = 1;
-    dirLight.shadow.camera.far = 10;
-
-    dirLight.shadow.camera.right = 1;
-    dirLight.shadow.camera.left = -1;
-    dirLight.shadow.camera.top = 1;
-    dirLight.shadow.camera.bottom = -1;
-
-    dirLight.shadow.mapSize.width = 1024;
-    dirLight.shadow.mapSize.height = 1024;
-    scene.add(dirLight);
-
-    // Geometry
-
-    clipMaterial = new THREE.MeshPhongMaterial({
-        color: 0xee0a10,
-        shininess: 100,
-        side: THREE.DoubleSide,
-        // Clipping setup:
-        clippingPlanes: createPlanes(Planes.length),
-        clipShadows: true,
-    });
-
-    object = new THREE.Group();
-
-    const geometry = new THREE.BoxGeometry(0.18, 0.18, 0.18);
-
-    for (let z = -2; z <= 2; ++z)
-        for (let y = -2; y <= 2; ++y)
-            for (let x = -2; x <= 2; ++x) {
-                const mesh = new THREE.Mesh(geometry, clipMaterial);
-                mesh.position.set(x / 5, y / 5, z / 5);
-                mesh.castShadow = true;
-                object.add(mesh);
-            }
-
-    scene.add(object);
-
-    const planeGeometry = new THREE.PlaneGeometry(3, 3, 1, 1),
-        color = new THREE.Color();
-
-    volumeVisualization = new THREE.Group();
-    volumeVisualization.visible = false;
-
-    for (let i = 0, n = Planes.length; i !== n; ++i) {
-        const material = new THREE.MeshBasicMaterial({
-            color: color.setHSL(i / n, 0.5, 0.5).getHex(),
-            side: THREE.DoubleSide,
-
-            opacity: 0.2,
-            transparent: true,
-
-            // clip to the others to show the volume (wildly
-            // intersecting transparent planes look bad)
-            clippingPlanes: clipMaterial.clippingPlanes.filter(function (_, j) {
-                return j !== i;
-            }),
-
-            // no need to enable shadow clipping - the plane
-            // visualization does not cast shadows
-        });
-
-        const mesh = new THREE.Mesh(planeGeometry, material);
-        mesh.matrixAutoUpdate = false;
-
-        volumeVisualization.add(mesh);
-    }
-
-    scene.add(volumeVisualization);
-
-    const ground = new THREE.Mesh(
-        planeGeometry,
-        new THREE.MeshPhongMaterial({
-            color: 0xa0adaf,
-            shininess: 10,
-        }),
-    );
-    ground.rotation.x = -Math.PI / 2;
-    ground.scale.multiplyScalar(3);
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // Renderer
-
-    const container = document.body;
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    container.appendChild(renderer.domElement);
-    // Clipping setup:
-    globalClippingPlanes = createPlanes(GlobalClippingPlanes.length);
-    renderer.clippingPlanes = Empty;
-    renderer.localClippingEnabled = true;
-
-    window.addEventListener('resize', onWindowResize);
-
-    // Stats
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // Controls
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 8;
-    controls.target.set(0, 1, 0);
-    controls.update();
-
-    // GUI
-
-    const gui = new GUI(),
-        folder = gui.addFolder('Local Clipping'),
-        props = {
-            get Enabled() {
-                return renderer.localClippingEnabled;
-            },
-            set Enabled(v) {
-                renderer.localClippingEnabled = v;
-                if (!v) volumeVisualization.visible = false;
-            },
-
-            get Shadows() {
-                return clipMaterial.clipShadows;
-            },
-            set Shadows(v) {
-                clipMaterial.clipShadows = v;
-            },
-
-            get Visualize() {
-                return volumeVisualization.visible;
-            },
-            set Visualize(v) {
-                if (renderer.localClippingEnabled) volumeVisualization.visible = v;
-            },
-        };
-
-    folder.add(props, 'Enabled');
-    folder.add(props, 'Shadows');
-    folder.add(props, 'Visualize').listen();
-
-    gui.addFolder('Global Clipping').add(
-        {
-            get Enabled() {
-                return renderer.clippingPlanes !== Empty;
-            },
-            set Enabled(v) {
-                renderer.clippingPlanes = v ? globalClippingPlanes : Empty;
-            },
-        },
-        'Enabled',
-    );
-
-    // Start
-
-    startTime = Date.now();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function setObjectWorldMatrix(object, matrix) {
-    // set the orientation of an object based on a world matrix
-
-    const parent = object.parent;
-    scene.updateMatrixWorld();
-    object.matrix.copy(parent.matrixWorld).invert();
-    object.applyMatrix4(matrix);
-}
-
-const transform = new THREE.Matrix4(),
-    tmpMatrix = new THREE.Matrix4();
-
-function animate() {
-    const currentTime = Date.now(),
-        time = (currentTime - startTime) / 1000;
-
-    object.position.y = 1;
-    object.rotation.x = time * 0.5;
-    object.rotation.y = time * 0.2;
-
-    object.updateMatrix();
-    transform.copy(object.matrix);
-
-    const bouncy = Math.cos(time * 0.5) * 0.5 + 0.7;
-    transform.multiply(tmpMatrix.makeScale(bouncy, bouncy, bouncy));
-
-    assignTransformedPlanes(clipMaterial.clippingPlanes, Planes, transform);
-
-    const planeMeshes = volumeVisualization.children;
-
-    for (let i = 0, n = planeMeshes.length; i !== n; ++i) {
-        tmpMatrix.multiplyMatrices(transform, PlaneMatrices[i]);
-        setObjectWorldMatrix(planeMeshes[i], tmpMatrix);
-    }
-
-    transform.makeRotationY(time * 0.1);
-
-    assignTransformedPlanes(globalClippingPlanes, GlobalClippingPlanes, transform);
-
-    stats.begin();
-    renderer.render(scene, camera);
-    stats.end();
-}
-
-init();
diff --git a/examples-testing/examples/webgl_clipping_intersection.ts b/examples-testing/examples/webgl_clipping_intersection.ts
deleted file mode 100644
index 5f45e45df..000000000
--- a/examples-testing/examples/webgl_clipping_intersection.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-const params = {
-    clipIntersection: true,
-    planeConstant: 0,
-    showHelpers: false,
-    alphaToCoverage: true,
-};
-
-const clipPlanes = [
-    new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
-    new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
-    new THREE.Plane(new THREE.Vector3(0, 0, -1), 0),
-];
-
-init();
-render();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.localClippingEnabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
-
-    camera.position.set(-1.5, 2.5, 3.0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use only if there is no animation loop
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-    controls.enablePan = false;
-
-    const light = new THREE.HemisphereLight(0xffffff, 0x080808, 4.5);
-    light.position.set(-1.25, 1, 1.25);
-    scene.add(light);
-
-    //
-
-    const group = new THREE.Group();
-
-    for (let i = 1; i <= 30; i += 2) {
-        const geometry = new THREE.SphereGeometry(i / 30, 48, 24);
-
-        const material = new THREE.MeshPhongMaterial({
-            color: new THREE.Color().setHSL(Math.random(), 0.5, 0.5, THREE.SRGBColorSpace),
-            side: THREE.DoubleSide,
-            clippingPlanes: clipPlanes,
-            clipIntersection: params.clipIntersection,
-            alphaToCoverage: true,
-        });
-
-        group.add(new THREE.Mesh(geometry, material));
-    }
-
-    scene.add(group);
-
-    // helpers
-
-    const helpers = new THREE.Group();
-    helpers.add(new THREE.PlaneHelper(clipPlanes[0], 2, 0xff0000));
-    helpers.add(new THREE.PlaneHelper(clipPlanes[1], 2, 0x00ff00));
-    helpers.add(new THREE.PlaneHelper(clipPlanes[2], 2, 0x0000ff));
-    helpers.visible = false;
-    scene.add(helpers);
-
-    // gui
-
-    const gui = new GUI();
-
-    gui.add(params, 'alphaToCoverage').onChange(function (value) {
-        group.children.forEach(c => {
-            c.material.alphaToCoverage = Boolean(value);
-            c.material.needsUpdate = true;
-        });
-
-        render();
-    });
-
-    gui.add(params, 'clipIntersection')
-        .name('clip intersection')
-        .onChange(function (value) {
-            const children = group.children;
-
-            for (let i = 0; i < children.length; i++) {
-                children[i].material.clipIntersection = value;
-            }
-
-            render();
-        });
-
-    gui.add(params, 'planeConstant', -1, 1)
-        .step(0.01)
-        .name('plane constant')
-        .onChange(function (value) {
-            for (let j = 0; j < clipPlanes.length; j++) {
-                clipPlanes[j].constant = value;
-            }
-
-            render();
-        });
-
-    gui.add(params, 'showHelpers')
-        .name('show helpers')
-        .onChange(function (value) {
-            helpers.visible = value;
-
-            render();
-        });
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_clipping_stencil.ts b/examples-testing/examples/webgl_clipping_stencil.ts
deleted file mode 100644
index ecb6b42b8..000000000
--- a/examples-testing/examples/webgl_clipping_stencil.ts
+++ /dev/null
@@ -1,260 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, object, stats;
-let planes, planeObjects, planeHelpers;
-let clock;
-
-const params = {
-    animate: true,
-    planeX: {
-        constant: 0,
-        negated: false,
-        displayHelper: false,
-    },
-    planeY: {
-        constant: 0,
-        negated: false,
-        displayHelper: false,
-    },
-    planeZ: {
-        constant: 0,
-        negated: false,
-        displayHelper: false,
-    },
-};
-
-init();
-
-function createPlaneStencilGroup(geometry, plane, renderOrder) {
-    const group = new THREE.Group();
-    const baseMat = new THREE.MeshBasicMaterial();
-    baseMat.depthWrite = false;
-    baseMat.depthTest = false;
-    baseMat.colorWrite = false;
-    baseMat.stencilWrite = true;
-    baseMat.stencilFunc = THREE.AlwaysStencilFunc;
-
-    // back faces
-    const mat0 = baseMat.clone();
-    mat0.side = THREE.BackSide;
-    mat0.clippingPlanes = [plane];
-    mat0.stencilFail = THREE.IncrementWrapStencilOp;
-    mat0.stencilZFail = THREE.IncrementWrapStencilOp;
-    mat0.stencilZPass = THREE.IncrementWrapStencilOp;
-
-    const mesh0 = new THREE.Mesh(geometry, mat0);
-    mesh0.renderOrder = renderOrder;
-    group.add(mesh0);
-
-    // front faces
-    const mat1 = baseMat.clone();
-    mat1.side = THREE.FrontSide;
-    mat1.clippingPlanes = [plane];
-    mat1.stencilFail = THREE.DecrementWrapStencilOp;
-    mat1.stencilZFail = THREE.DecrementWrapStencilOp;
-    mat1.stencilZPass = THREE.DecrementWrapStencilOp;
-
-    const mesh1 = new THREE.Mesh(geometry, mat1);
-    mesh1.renderOrder = renderOrder;
-
-    group.add(mesh1);
-
-    return group;
-}
-
-function init() {
-    clock = new THREE.Clock();
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(2, 2, 2);
-
-    scene.add(new THREE.AmbientLight(0xffffff, 1.5));
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(5, 10, 7.5);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.right = 2;
-    dirLight.shadow.camera.left = -2;
-    dirLight.shadow.camera.top = 2;
-    dirLight.shadow.camera.bottom = -2;
-
-    dirLight.shadow.mapSize.width = 1024;
-    dirLight.shadow.mapSize.height = 1024;
-    scene.add(dirLight);
-
-    planes = [
-        new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0),
-        new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
-        new THREE.Plane(new THREE.Vector3(0, 0, -1), 0),
-    ];
-
-    planeHelpers = planes.map(p => new THREE.PlaneHelper(p, 2, 0xffffff));
-    planeHelpers.forEach(ph => {
-        ph.visible = false;
-        scene.add(ph);
-    });
-
-    const geometry = new THREE.TorusKnotGeometry(0.4, 0.15, 220, 60);
-    object = new THREE.Group();
-    scene.add(object);
-
-    // Set up clip plane rendering
-    planeObjects = [];
-    const planeGeom = new THREE.PlaneGeometry(4, 4);
-
-    for (let i = 0; i < 3; i++) {
-        const poGroup = new THREE.Group();
-        const plane = planes[i];
-        const stencilGroup = createPlaneStencilGroup(geometry, plane, i + 1);
-
-        // plane is clipped by the other clipping planes
-        const planeMat = new THREE.MeshStandardMaterial({
-            color: 0xe91e63,
-            metalness: 0.1,
-            roughness: 0.75,
-            clippingPlanes: planes.filter(p => p !== plane),
-
-            stencilWrite: true,
-            stencilRef: 0,
-            stencilFunc: THREE.NotEqualStencilFunc,
-            stencilFail: THREE.ReplaceStencilOp,
-            stencilZFail: THREE.ReplaceStencilOp,
-            stencilZPass: THREE.ReplaceStencilOp,
-        });
-        const po = new THREE.Mesh(planeGeom, planeMat);
-        po.onAfterRender = function (renderer) {
-            renderer.clearStencil();
-        };
-
-        po.renderOrder = i + 1.1;
-
-        object.add(stencilGroup);
-        poGroup.add(po);
-        planeObjects.push(po);
-        scene.add(poGroup);
-    }
-
-    const material = new THREE.MeshStandardMaterial({
-        color: 0xffc107,
-        metalness: 0.1,
-        roughness: 0.75,
-        clippingPlanes: planes,
-        clipShadows: true,
-        shadowSide: THREE.DoubleSide,
-    });
-
-    // add the color
-    const clippedColorFront = new THREE.Mesh(geometry, material);
-    clippedColorFront.castShadow = true;
-    clippedColorFront.renderOrder = 6;
-    object.add(clippedColorFront);
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(9, 9, 1, 1),
-        new THREE.ShadowMaterial({ color: 0x000000, opacity: 0.25, side: THREE.DoubleSide }),
-    );
-
-    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
-    ground.position.y = -1;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // Renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true, stencil: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setClearColor(0x263238);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.localClippingEnabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    // Stats
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    // Controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 20;
-    controls.update();
-
-    // GUI
-    const gui = new GUI();
-    gui.add(params, 'animate');
-
-    const planeX = gui.addFolder('planeX');
-    planeX.add(params.planeX, 'displayHelper').onChange(v => (planeHelpers[0].visible = v));
-    planeX
-        .add(params.planeX, 'constant')
-        .min(-1)
-        .max(1)
-        .onChange(d => (planes[0].constant = d));
-    planeX.add(params.planeX, 'negated').onChange(() => {
-        planes[0].negate();
-        params.planeX.constant = planes[0].constant;
-    });
-    planeX.open();
-
-    const planeY = gui.addFolder('planeY');
-    planeY.add(params.planeY, 'displayHelper').onChange(v => (planeHelpers[1].visible = v));
-    planeY
-        .add(params.planeY, 'constant')
-        .min(-1)
-        .max(1)
-        .onChange(d => (planes[1].constant = d));
-    planeY.add(params.planeY, 'negated').onChange(() => {
-        planes[1].negate();
-        params.planeY.constant = planes[1].constant;
-    });
-    planeY.open();
-
-    const planeZ = gui.addFolder('planeZ');
-    planeZ.add(params.planeZ, 'displayHelper').onChange(v => (planeHelpers[2].visible = v));
-    planeZ
-        .add(params.planeZ, 'constant')
-        .min(-1)
-        .max(1)
-        .onChange(d => (planes[2].constant = d));
-    planeZ.add(params.planeZ, 'negated').onChange(() => {
-        planes[2].negate();
-        params.planeZ.constant = planes[2].constant;
-    });
-    planeZ.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (params.animate) {
-        object.rotation.x += delta * 0.5;
-        object.rotation.y += delta * 0.2;
-    }
-
-    for (let i = 0; i < planeObjects.length; i++) {
-        const plane = planes[i];
-        const po = planeObjects[i];
-        plane.coplanarPoint(po.position);
-        po.lookAt(po.position.x - plane.normal.x, po.position.y - plane.normal.y, po.position.z - plane.normal.z);
-    }
-
-    stats.begin();
-    renderer.render(scene, camera);
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_custom_attributes.ts b/examples-testing/examples/webgl_custom_attributes.ts
deleted file mode 100644
index 0dc897748..000000000
--- a/examples-testing/examples/webgl_custom_attributes.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let renderer, scene, camera, stats;
-
-let sphere, uniforms;
-
-let displacement, noise;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    uniforms = {
-        amplitude: { value: 1.0 },
-        color: { value: new THREE.Color(0xff2200) },
-        colorTexture: { value: new THREE.TextureLoader().load('textures/water.jpg') },
-    };
-
-    uniforms['colorTexture'].value.wrapS = uniforms['colorTexture'].value.wrapT = THREE.RepeatWrapping;
-
-    const shaderMaterial = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    const radius = 50,
-        segments = 128,
-        rings = 64;
-
-    const geometry = new THREE.SphereGeometry(radius, segments, rings);
-
-    displacement = new Float32Array(geometry.attributes.position.count);
-    noise = new Float32Array(geometry.attributes.position.count);
-
-    for (let i = 0; i < displacement.length; i++) {
-        noise[i] = Math.random() * 5;
-    }
-
-    geometry.setAttribute('displacement', new THREE.BufferAttribute(displacement, 1));
-
-    sphere = new THREE.Mesh(geometry, shaderMaterial);
-    scene.add(sphere);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.01;
-
-    sphere.rotation.y = sphere.rotation.z = 0.01 * time;
-
-    uniforms['amplitude'].value = 2.5 * Math.sin(sphere.rotation.y * 0.125);
-    uniforms['color'].value.offsetHSL(0.0005, 0, 0);
-
-    for (let i = 0; i < displacement.length; i++) {
-        displacement[i] = Math.sin(0.1 * i + time);
-
-        noise[i] += 0.5 * (0.5 - Math.random());
-        noise[i] = THREE.MathUtils.clamp(noise[i], -5, 5);
-
-        displacement[i] += noise[i];
-    }
-
-    sphere.geometry.attributes.displacement.needsUpdate = true;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_custom_attributes_lines.ts b/examples-testing/examples/webgl_custom_attributes_lines.ts
deleted file mode 100644
index 3e2454e92..000000000
--- a/examples-testing/examples/webgl_custom_attributes_lines.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import * as THREE from 'three';
-
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let renderer, scene, camera, stats;
-
-let line, uniforms;
-
-const loader = new FontLoader();
-loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
-    init(font);
-});
-
-function init(font) {
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    uniforms = {
-        amplitude: { value: 5.0 },
-        opacity: { value: 0.3 },
-        color: { value: new THREE.Color(0xffffff) },
-    };
-
-    const shaderMaterial = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-        blending: THREE.AdditiveBlending,
-        depthTest: false,
-        transparent: true,
-    });
-
-    const geometry = new TextGeometry('three.js', {
-        font: font,
-
-        size: 50,
-        depth: 15,
-        curveSegments: 10,
-
-        bevelThickness: 5,
-        bevelSize: 1.5,
-        bevelEnabled: true,
-        bevelSegments: 10,
-    });
-
-    geometry.center();
-
-    const count = geometry.attributes.position.count;
-
-    const displacement = new THREE.Float32BufferAttribute(count * 3, 3);
-    geometry.setAttribute('displacement', displacement);
-
-    const customColor = new THREE.Float32BufferAttribute(count * 3, 3);
-    geometry.setAttribute('customColor', customColor);
-
-    const color = new THREE.Color(0xffffff);
-
-    for (let i = 0, l = customColor.count; i < l; i++) {
-        color.setHSL(i / l, 0.5, 0.5);
-        color.toArray(customColor.array, i * customColor.itemSize);
-    }
-
-    line = new THREE.Line(geometry, shaderMaterial);
-    line.rotation.x = 0.2;
-    scene.add(line);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.001;
-
-    line.rotation.y = 0.25 * time;
-
-    uniforms.amplitude.value = Math.sin(0.5 * time);
-    uniforms.color.value.offsetHSL(0.0005, 0, 0);
-
-    const attributes = line.geometry.attributes;
-    const array = attributes.displacement.array;
-
-    for (let i = 0, l = array.length; i < l; i += 3) {
-        array[i] += 0.3 * (0.5 - Math.random());
-        array[i + 1] += 0.3 * (0.5 - Math.random());
-        array[i + 2] += 0.3 * (0.5 - Math.random());
-    }
-
-    attributes.displacement.needsUpdate = true;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_custom_attributes_points.ts b/examples-testing/examples/webgl_custom_attributes_points.ts
deleted file mode 100644
index ae112980a..000000000
--- a/examples-testing/examples/webgl_custom_attributes_points.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let renderer, scene, camera, stats;
-
-let sphere;
-
-const WIDTH = window.innerWidth;
-const HEIGHT = window.innerHeight;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 10000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-
-    const amount = 100000;
-    const radius = 200;
-
-    const positions = new Float32Array(amount * 3);
-    const colors = new Float32Array(amount * 3);
-    const sizes = new Float32Array(amount);
-
-    const vertex = new THREE.Vector3();
-    const color = new THREE.Color(0xffffff);
-
-    for (let i = 0; i < amount; i++) {
-        vertex.x = (Math.random() * 2 - 1) * radius;
-        vertex.y = (Math.random() * 2 - 1) * radius;
-        vertex.z = (Math.random() * 2 - 1) * radius;
-        vertex.toArray(positions, i * 3);
-
-        if (vertex.x < 0) {
-            color.setHSL(0.5 + 0.1 * (i / amount), 0.7, 0.5);
-        } else {
-            color.setHSL(0.0 + 0.1 * (i / amount), 0.9, 0.5);
-        }
-
-        color.toArray(colors, i * 3);
-
-        sizes[i] = 10;
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    geometry.setAttribute('customColor', new THREE.BufferAttribute(colors, 3));
-    geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
-
-    //
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            color: { value: new THREE.Color(0xffffff) },
-            pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/spark1.png') },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-
-        blending: THREE.AdditiveBlending,
-        depthTest: false,
-        transparent: true,
-    });
-
-    //
-
-    sphere = new THREE.Points(geometry, material);
-    scene.add(sphere);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(WIDTH, HEIGHT);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.005;
-
-    sphere.rotation.z = 0.01 * time;
-
-    const geometry = sphere.geometry;
-    const attributes = geometry.attributes;
-
-    for (let i = 0; i < attributes.size.array.length; i++) {
-        attributes.size.array[i] = 14 + 13 * Math.sin(0.1 * i + time);
-    }
-
-    attributes.size.needsUpdate = true;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_custom_attributes_points2.ts b/examples-testing/examples/webgl_custom_attributes_points2.ts
deleted file mode 100644
index edd158fa1..000000000
--- a/examples-testing/examples/webgl_custom_attributes_points2.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let renderer, scene, camera, stats;
-let sphere, length1;
-
-const WIDTH = window.innerWidth;
-const HEIGHT = window.innerHeight;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 1, 10000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-
-    const radius = 100,
-        segments = 68,
-        rings = 38;
-
-    let sphereGeometry = new THREE.SphereGeometry(radius, segments, rings);
-    let boxGeometry = new THREE.BoxGeometry(0.8 * radius, 0.8 * radius, 0.8 * radius, 10, 10, 10);
-
-    // if normal and uv attributes are not removed, mergeVertices() can't consolidate identical vertices with different normal/uv data
-
-    sphereGeometry.deleteAttribute('normal');
-    sphereGeometry.deleteAttribute('uv');
-
-    boxGeometry.deleteAttribute('normal');
-    boxGeometry.deleteAttribute('uv');
-
-    sphereGeometry = BufferGeometryUtils.mergeVertices(sphereGeometry);
-    boxGeometry = BufferGeometryUtils.mergeVertices(boxGeometry);
-
-    const combinedGeometry = BufferGeometryUtils.mergeGeometries([sphereGeometry, boxGeometry]);
-    const positionAttribute = combinedGeometry.getAttribute('position');
-
-    const colors = [];
-    const sizes = [];
-
-    const color = new THREE.Color();
-    const vertex = new THREE.Vector3();
-
-    length1 = sphereGeometry.getAttribute('position').count;
-
-    for (let i = 0, l = positionAttribute.count; i < l; i++) {
-        vertex.fromBufferAttribute(positionAttribute, i);
-
-        if (i < length1) {
-            color.setHSL(0.01 + 0.1 * (i / length1), 0.99, (vertex.y + radius) / (4 * radius));
-        } else {
-            color.setHSL(0.6, 0.75, 0.25 + vertex.y / (2 * radius));
-        }
-
-        color.toArray(colors, i * 3);
-
-        sizes[i] = i < length1 ? 10 : 40;
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
-    geometry.setAttribute('ca', new THREE.Float32BufferAttribute(colors, 3));
-
-    //
-
-    const texture = new THREE.TextureLoader().load('textures/sprites/disc.png');
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.wrapT = THREE.RepeatWrapping;
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            color: { value: new THREE.Color(0xffffff) },
-            pointTexture: { value: texture },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-        transparent: true,
-    });
-
-    //
-
-    sphere = new THREE.Points(geometry, material);
-    scene.add(sphere);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(WIDTH, HEIGHT);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function sortPoints() {
-    const vector = new THREE.Vector3();
-
-    // Model View Projection matrix
-
-    const matrix = new THREE.Matrix4();
-    matrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
-    matrix.multiply(sphere.matrixWorld);
-
-    //
-
-    const geometry = sphere.geometry;
-
-    let index = geometry.getIndex();
-    const positions = geometry.getAttribute('position').array;
-    const length = positions.length / 3;
-
-    if (index === null) {
-        const array = new Uint16Array(length);
-
-        for (let i = 0; i < length; i++) {
-            array[i] = i;
-        }
-
-        index = new THREE.BufferAttribute(array, 1);
-
-        geometry.setIndex(index);
-    }
-
-    const sortArray = [];
-
-    for (let i = 0; i < length; i++) {
-        vector.fromArray(positions, i * 3);
-        vector.applyMatrix4(matrix);
-
-        sortArray.push([vector.z, i]);
-    }
-
-    function numericalSort(a, b) {
-        return b[0] - a[0];
-    }
-
-    sortArray.sort(numericalSort);
-
-    const indices = index.array;
-
-    for (let i = 0; i < length; i++) {
-        indices[i] = sortArray[i][1];
-    }
-
-    geometry.index.needsUpdate = true;
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.005;
-
-    sphere.rotation.y = 0.02 * time;
-    sphere.rotation.z = 0.02 * time;
-
-    const geometry = sphere.geometry;
-    const attributes = geometry.attributes;
-
-    for (let i = 0; i < attributes.size.array.length; i++) {
-        if (i < length1) {
-            attributes.size.array[i] = 16 + 12 * Math.sin(0.1 * i + time);
-        }
-    }
-
-    attributes.size.needsUpdate = true;
-
-    sortPoints();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_custom_attributes_points3.ts b/examples-testing/examples/webgl_custom_attributes_points3.ts
deleted file mode 100644
index 1b46a805e..000000000
--- a/examples-testing/examples/webgl_custom_attributes_points3.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let renderer, scene, camera, stats;
-
-let object;
-
-let vertices1;
-
-const WIDTH = window.innerWidth;
-const HEIGHT = window.innerHeight;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 1000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    let radius = 100;
-    const inner = 0.6 * radius;
-    const vertex = new THREE.Vector3();
-    const vertices = [];
-
-    for (let i = 0; i < 100000; i++) {
-        vertex.x = Math.random() * 2 - 1;
-        vertex.y = Math.random() * 2 - 1;
-        vertex.z = Math.random() * 2 - 1;
-        vertex.multiplyScalar(radius);
-
-        if (
-            vertex.x > inner ||
-            vertex.x < -inner ||
-            vertex.y > inner ||
-            vertex.y < -inner ||
-            vertex.z > inner ||
-            vertex.z < -inner
-        )
-            vertices.push(vertex.x, vertex.y, vertex.z);
-    }
-
-    vertices1 = vertices.length / 3;
-
-    radius = 200;
-
-    let boxGeometry1 = new THREE.BoxGeometry(radius, 0.1 * radius, 0.1 * radius, 50, 5, 5);
-
-    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
-
-    boxGeometry1.deleteAttribute('normal');
-    boxGeometry1.deleteAttribute('uv');
-
-    boxGeometry1 = BufferGeometryUtils.mergeVertices(boxGeometry1);
-
-    const matrix = new THREE.Matrix4();
-    const position = new THREE.Vector3();
-    const rotation = new THREE.Euler();
-    const quaternion = new THREE.Quaternion();
-    const scale = new THREE.Vector3(1, 1, 1);
-
-    function addGeo(geo, x, y, z, ry) {
-        position.set(x, y, z);
-        rotation.set(0, ry, 0);
-
-        matrix.compose(position, quaternion.setFromEuler(rotation), scale);
-
-        const positionAttribute = geo.getAttribute('position');
-
-        for (let i = 0, l = positionAttribute.count; i < l; i++) {
-            vertex.fromBufferAttribute(positionAttribute, i);
-            vertex.applyMatrix4(matrix);
-            vertices.push(vertex.x, vertex.y, vertex.z);
-        }
-    }
-
-    // side 1
-
-    addGeo(boxGeometry1, 0, 110, 110, 0);
-    addGeo(boxGeometry1, 0, 110, -110, 0);
-    addGeo(boxGeometry1, 0, -110, 110, 0);
-    addGeo(boxGeometry1, 0, -110, -110, 0);
-
-    // side 2
-
-    addGeo(boxGeometry1, 110, 110, 0, Math.PI / 2);
-    addGeo(boxGeometry1, 110, -110, 0, Math.PI / 2);
-    addGeo(boxGeometry1, -110, 110, 0, Math.PI / 2);
-    addGeo(boxGeometry1, -110, -110, 0, Math.PI / 2);
-
-    // corner edges
-
-    let boxGeometry2 = new THREE.BoxGeometry(0.1 * radius, radius * 1.2, 0.1 * radius, 5, 60, 5);
-
-    boxGeometry2.deleteAttribute('normal');
-    boxGeometry2.deleteAttribute('uv');
-
-    boxGeometry2 = BufferGeometryUtils.mergeVertices(boxGeometry2);
-
-    addGeo(boxGeometry2, 110, 0, 110, 0);
-    addGeo(boxGeometry2, 110, 0, -110, 0);
-    addGeo(boxGeometry2, -110, 0, 110, 0);
-    addGeo(boxGeometry2, -110, 0, -110, 0);
-
-    const positionAttribute = new THREE.Float32BufferAttribute(vertices, 3);
-
-    const colors = [];
-    const sizes = [];
-
-    const color = new THREE.Color();
-
-    for (let i = 0; i < positionAttribute.count; i++) {
-        if (i < vertices1) {
-            color.setHSL(0.5 + 0.2 * (i / vertices1), 1, 0.5);
-        } else {
-            color.setHSL(0.1, 1, 0.5);
-        }
-
-        color.toArray(colors, i * 3);
-
-        sizes[i] = i < vertices1 ? 10 : 40;
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('ca', new THREE.Float32BufferAttribute(colors, 3));
-    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
-
-    //
-
-    const texture = new THREE.TextureLoader().load('textures/sprites/ball.png');
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.wrapT = THREE.RepeatWrapping;
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            amplitude: { value: 1.0 },
-            color: { value: new THREE.Color(0xffffff) },
-            pointTexture: { value: texture },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    //
-
-    object = new THREE.Points(geometry, material);
-    scene.add(object);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(WIDTH, HEIGHT);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.01;
-
-    object.rotation.y = object.rotation.z = 0.02 * time;
-
-    const geometry = object.geometry;
-    const attributes = geometry.attributes;
-
-    for (let i = 0; i < attributes.size.array.length; i++) {
-        if (i < vertices1) {
-            attributes.size.array[i] = Math.max(0, 26 + 32 * Math.sin(0.1 * i + 0.6 * time));
-        }
-    }
-
-    attributes.size.needsUpdate = true;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_decals.ts b/examples-testing/examples/webgl_decals.ts
deleted file mode 100644
index 23cdb4da8..000000000
--- a/examples-testing/examples/webgl_decals.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DecalGeometry } from 'three/addons/geometries/DecalGeometry.js';
-
-const container = document.getElementById('container');
-
-let renderer, scene, camera, stats;
-let mesh;
-let raycaster;
-let line;
-
-const intersection = {
-    intersects: false,
-    point: new THREE.Vector3(),
-    normal: new THREE.Vector3(),
-};
-const mouse = new THREE.Vector2();
-const intersects = [];
-
-const textureLoader = new THREE.TextureLoader();
-const decalDiffuse = textureLoader.load('textures/decal/decal-diffuse.png');
-decalDiffuse.colorSpace = THREE.SRGBColorSpace;
-const decalNormal = textureLoader.load('textures/decal/decal-normal.jpg');
-
-const decalMaterial = new THREE.MeshPhongMaterial({
-    specular: 0x444444,
-    map: decalDiffuse,
-    normalMap: decalNormal,
-    normalScale: new THREE.Vector2(1, 1),
-    shininess: 30,
-    transparent: true,
-    depthTest: true,
-    depthWrite: false,
-    polygonOffset: true,
-    polygonOffsetFactor: -4,
-    wireframe: false,
-});
-
-const decals = [];
-let mouseHelper;
-const position = new THREE.Vector3();
-const orientation = new THREE.Euler();
-const size = new THREE.Vector3(10, 10, 10);
-
-const params = {
-    minScale: 10,
-    maxScale: 20,
-    rotate: true,
-    clear: function () {
-        removeDecals();
-    },
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 120;
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 50;
-    controls.maxDistance = 200;
-
-    scene.add(new THREE.AmbientLight(0x666666));
-
-    const dirLight1 = new THREE.DirectionalLight(0xffddcc, 3);
-    dirLight1.position.set(1, 0.75, 0.5);
-    scene.add(dirLight1);
-
-    const dirLight2 = new THREE.DirectionalLight(0xccccff, 3);
-    dirLight2.position.set(-1, 0.75, -0.5);
-    scene.add(dirLight2);
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setFromPoints([new THREE.Vector3(), new THREE.Vector3()]);
-
-    line = new THREE.Line(geometry, new THREE.LineBasicMaterial());
-    scene.add(line);
-
-    loadLeePerrySmith();
-
-    raycaster = new THREE.Raycaster();
-
-    mouseHelper = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 10), new THREE.MeshNormalMaterial());
-    mouseHelper.visible = false;
-    scene.add(mouseHelper);
-
-    window.addEventListener('resize', onWindowResize);
-
-    let moved = false;
-
-    controls.addEventListener('change', function () {
-        moved = true;
-    });
-
-    window.addEventListener('pointerdown', function () {
-        moved = false;
-    });
-
-    window.addEventListener('pointerup', function (event) {
-        if (moved === false) {
-            checkIntersection(event.clientX, event.clientY);
-
-            if (intersection.intersects) shoot();
-        }
-    });
-
-    window.addEventListener('pointermove', onPointerMove);
-
-    function onPointerMove(event) {
-        if (event.isPrimary) {
-            checkIntersection(event.clientX, event.clientY);
-        }
-    }
-
-    function checkIntersection(x, y) {
-        if (mesh === undefined) return;
-
-        mouse.x = (x / window.innerWidth) * 2 - 1;
-        mouse.y = -(y / window.innerHeight) * 2 + 1;
-
-        raycaster.setFromCamera(mouse, camera);
-        raycaster.intersectObject(mesh, false, intersects);
-
-        if (intersects.length > 0) {
-            const p = intersects[0].point;
-            mouseHelper.position.copy(p);
-            intersection.point.copy(p);
-
-            const n = intersects[0].face.normal.clone();
-            n.transformDirection(mesh.matrixWorld);
-            n.multiplyScalar(10);
-            n.add(intersects[0].point);
-
-            intersection.normal.copy(intersects[0].face.normal);
-            mouseHelper.lookAt(n);
-
-            const positions = line.geometry.attributes.position;
-            positions.setXYZ(0, p.x, p.y, p.z);
-            positions.setXYZ(1, n.x, n.y, n.z);
-            positions.needsUpdate = true;
-
-            intersection.intersects = true;
-
-            intersects.length = 0;
-        } else {
-            intersection.intersects = false;
-        }
-    }
-
-    const gui = new GUI();
-
-    gui.add(params, 'minScale', 1, 30);
-    gui.add(params, 'maxScale', 1, 30);
-    gui.add(params, 'rotate');
-    gui.add(params, 'clear');
-    gui.open();
-}
-
-function loadLeePerrySmith() {
-    const map = textureLoader.load('models/gltf/LeePerrySmith/Map-COL.jpg');
-    map.colorSpace = THREE.SRGBColorSpace;
-    const specularMap = textureLoader.load('models/gltf/LeePerrySmith/Map-SPEC.jpg');
-    const normalMap = textureLoader.load('models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg');
-
-    const loader = new GLTFLoader();
-
-    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        mesh = gltf.scene.children[0];
-        mesh.material = new THREE.MeshPhongMaterial({
-            specular: 0x111111,
-            map: map,
-            specularMap: specularMap,
-            normalMap: normalMap,
-            shininess: 25,
-        });
-
-        scene.add(mesh);
-        mesh.scale.set(10, 10, 10);
-    });
-}
-
-function shoot() {
-    position.copy(intersection.point);
-    orientation.copy(mouseHelper.rotation);
-
-    if (params.rotate) orientation.z = Math.random() * 2 * Math.PI;
-
-    const scale = params.minScale + Math.random() * (params.maxScale - params.minScale);
-    size.set(scale, scale, scale);
-
-    const material = decalMaterial.clone();
-    material.color.setHex(Math.random() * 0xffffff);
-
-    const m = new THREE.Mesh(new DecalGeometry(mesh, position, orientation, size), material);
-    m.renderOrder = decals.length; // give decals a fixed render order
-
-    decals.push(m);
-    scene.add(m);
-}
-
-function removeDecals() {
-    decals.forEach(function (d) {
-        scene.remove(d);
-    });
-
-    decals.length = 0;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_effects_anaglyph.ts b/examples-testing/examples/webgl_effects_anaglyph.ts
deleted file mode 100644
index 8415973df..000000000
--- a/examples-testing/examples/webgl_effects_anaglyph.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as THREE from 'three';
-
-import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';
-
-let container, camera, scene, renderer, effect;
-
-const spheres = [];
-
-let mouseX = 0;
-let mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-document.addEventListener('mousemove', onDocumentMouseMove);
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
-    camera.position.z = 3;
-
-    const path = 'textures/cube/pisa/';
-    const format = '.png';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const textureCube = new THREE.CubeTextureLoader().load(urls);
-
-    scene = new THREE.Scene();
-    scene.background = textureCube;
-
-    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = Math.random() * 10 - 5;
-        mesh.position.y = Math.random() * 10 - 5;
-        mesh.position.z = Math.random() * 10 - 5;
-
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
-
-        scene.add(mesh);
-
-        spheres.push(mesh);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    const width = window.innerWidth || 2;
-    const height = window.innerHeight || 2;
-
-    effect = new AnaglyphEffect(renderer);
-    effect.setSize(width, height);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    effect.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) / 100;
-    mouseY = (event.clientY - windowHalfY) / 100;
-}
-
-//
-
-function animate() {
-    render();
-}
-
-function render() {
-    const timer = 0.0001 * Date.now();
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0, il = spheres.length; i < il; i++) {
-        const sphere = spheres[i];
-
-        sphere.position.x = 5 * Math.cos(timer + i);
-        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
-    }
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_effects_ascii.ts b/examples-testing/examples/webgl_effects_ascii.ts
deleted file mode 100644
index a412bb79e..000000000
--- a/examples-testing/examples/webgl_effects_ascii.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as THREE from 'three';
-
-import { AsciiEffect } from 'three/addons/effects/AsciiEffect.js';
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-
-let camera, controls, scene, renderer, effect;
-
-let sphere, plane;
-
-const start = Date.now();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.y = 150;
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0, 0, 0);
-
-    const pointLight1 = new THREE.PointLight(0xffffff, 3, 0, 0);
-    pointLight1.position.set(500, 500, 500);
-    scene.add(pointLight1);
-
-    const pointLight2 = new THREE.PointLight(0xffffff, 1, 0, 0);
-    pointLight2.position.set(-500, -500, -500);
-    scene.add(pointLight2);
-
-    sphere = new THREE.Mesh(new THREE.SphereGeometry(200, 20, 10), new THREE.MeshPhongMaterial({ flatShading: true }));
-    scene.add(sphere);
-
-    // Plane
-
-    plane = new THREE.Mesh(new THREE.PlaneGeometry(400, 400), new THREE.MeshBasicMaterial({ color: 0xe0e0e0 }));
-    plane.position.y = -200;
-    plane.rotation.x = -Math.PI / 2;
-    scene.add(plane);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    effect = new AsciiEffect(renderer, ' .:-+*=%@#', { invert: true });
-    effect.setSize(window.innerWidth, window.innerHeight);
-    effect.domElement.style.color = 'white';
-    effect.domElement.style.backgroundColor = 'black';
-
-    // Special case: append effect.domElement, instead of renderer.domElement.
-    // AsciiEffect creates a custom domElement (a div container) where the ASCII elements are placed.
-
-    document.body.appendChild(effect.domElement);
-
-    controls = new TrackballControls(camera, effect.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    effect.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const timer = Date.now() - start;
-
-    sphere.position.y = Math.abs(Math.sin(timer * 0.002)) * 150;
-    sphere.rotation.x = timer * 0.0003;
-    sphere.rotation.z = timer * 0.0002;
-
-    controls.update();
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_effects_parallaxbarrier.ts b/examples-testing/examples/webgl_effects_parallaxbarrier.ts
deleted file mode 100644
index 90c867973..000000000
--- a/examples-testing/examples/webgl_effects_parallaxbarrier.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import * as THREE from 'three';
-
-import { ParallaxBarrierEffect } from 'three/addons/effects/ParallaxBarrierEffect.js';
-
-let container, camera, scene, renderer, effect;
-
-const spheres = [];
-
-let mouseX = 0;
-let mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-document.addEventListener('mousemove', onDocumentMouseMove);
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
-    camera.position.z = 3;
-
-    const path = 'textures/cube/pisa/';
-    const format = '.png';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const textureCube = new THREE.CubeTextureLoader().load(urls);
-
-    scene = new THREE.Scene();
-    scene.background = textureCube;
-
-    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = Math.random() * 10 - 5;
-        mesh.position.y = Math.random() * 10 - 5;
-        mesh.position.z = Math.random() * 10 - 5;
-
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
-
-        scene.add(mesh);
-
-        spheres.push(mesh);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    const width = window.innerWidth || 2;
-    const height = window.innerHeight || 2;
-
-    effect = new ParallaxBarrierEffect(renderer);
-    effect.setSize(width, height);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    effect.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) / 100;
-    mouseY = (event.clientY - windowHalfY) / 100;
-}
-
-//
-
-function animate() {
-    const timer = 0.0001 * Date.now();
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0, il = spheres.length; i < il; i++) {
-        const sphere = spheres[i];
-
-        sphere.position.x = 5 * Math.cos(timer + i);
-        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
-    }
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_effects_peppersghost.ts b/examples-testing/examples/webgl_effects_peppersghost.ts
deleted file mode 100644
index 41dfb4b65..000000000
--- a/examples-testing/examples/webgl_effects_peppersghost.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as THREE from 'three';
-
-import { PeppersGhostEffect } from 'three/addons/effects/PeppersGhostEffect.js';
-
-let container;
-
-let camera, scene, renderer, effect;
-let group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
-
-    scene = new THREE.Scene();
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    // Cube
-
-    const geometry = new THREE.BoxGeometry().toNonIndexed(); // ensure unique vertices for each triangle
-
-    const position = geometry.attributes.position;
-    const colors = [];
-    const color = new THREE.Color();
-
-    // generate for each side of the cube a different color
-
-    for (let i = 0; i < position.count; i += 6) {
-        color.setHex(Math.random() * 0xffffff);
-
-        // first face
-
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-
-        // second face
-
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-        colors.push(color.r, color.g, color.b);
-    }
-
-    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    const material = new THREE.MeshBasicMaterial({ vertexColors: true });
-
-    for (let i = 0; i < 10; i++) {
-        const cube = new THREE.Mesh(geometry, material);
-        cube.position.x = Math.random() * 2 - 1;
-        cube.position.y = Math.random() * 2 - 1;
-        cube.position.z = Math.random() * 2 - 1;
-        cube.scale.multiplyScalar(Math.random() + 0.5);
-        group.add(cube);
-    }
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    effect = new PeppersGhostEffect(renderer);
-    effect.setSize(window.innerWidth, window.innerHeight);
-    effect.cameraDistance = 5;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    effect.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    group.rotation.y += 0.01;
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_effects_stereo.ts b/examples-testing/examples/webgl_effects_stereo.ts
deleted file mode 100644
index dd2f61f91..000000000
--- a/examples-testing/examples/webgl_effects_stereo.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import * as THREE from 'three';
-
-import { StereoEffect } from 'three/addons/effects/StereoEffect.js';
-
-let container, camera, scene, renderer, effect;
-
-const spheres = [];
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-document.addEventListener('mousemove', onDocumentMouseMove);
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
-    camera.position.z = 3200;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.CubeTextureLoader()
-        .setPath('textures/cube/Park3Med/')
-        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
-
-    const geometry = new THREE.SphereGeometry(100, 32, 16);
-
-    const textureCube = new THREE.CubeTextureLoader()
-        .setPath('textures/cube/Park3Med/')
-        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
-    textureCube.mapping = THREE.CubeRefractionMapping;
-
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube, refractionRatio: 0.95 });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 10000 - 5000;
-        mesh.position.y = Math.random() * 10000 - 5000;
-        mesh.position.z = Math.random() * 10000 - 5000;
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
-        scene.add(mesh);
-
-        spheres.push(mesh);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    effect = new StereoEffect(renderer);
-    effect.setSize(window.innerWidth, window.innerHeight);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    effect.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) * 10;
-    mouseY = (event.clientY - windowHalfY) * 10;
-}
-
-//
-
-function animate() {
-    const timer = 0.0001 * Date.now();
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-    camera.lookAt(scene.position);
-
-    for (let i = 0, il = spheres.length; i < il; i++) {
-        const sphere = spheres[i];
-
-        sphere.position.x = 5000 * Math.cos(timer + i);
-        sphere.position.y = 5000 * Math.sin(timer + i * 1.1);
-    }
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_framebuffer_texture.ts b/examples-testing/examples/webgl_framebuffer_texture.ts
deleted file mode 100644
index df4acc9d6..000000000
--- a/examples-testing/examples/webgl_framebuffer_texture.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
-
-let camera, scene, renderer;
-let line, sprite, texture;
-
-let cameraOrtho, sceneOrtho;
-
-let offset = 0;
-
-const dpr = window.devicePixelRatio;
-
-const textureSize = 128 * dpr;
-const vector = new THREE.Vector2();
-const color = new THREE.Color();
-
-init();
-
-function init() {
-    //
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000);
-    camera.position.z = 20;
-
-    cameraOrtho = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 1, 10);
-    cameraOrtho.position.z = 10;
-
-    scene = new THREE.Scene();
-    sceneOrtho = new THREE.Scene();
-
-    //
-
-    const points = GeometryUtils.gosper(8);
-
-    const geometry = new THREE.BufferGeometry();
-    const positionAttribute = new THREE.Float32BufferAttribute(points, 3);
-    geometry.setAttribute('position', positionAttribute);
-    geometry.center();
-
-    const colorAttribute = new THREE.BufferAttribute(new Float32Array(positionAttribute.array.length), 3);
-    colorAttribute.setUsage(THREE.DynamicDrawUsage);
-    geometry.setAttribute('color', colorAttribute);
-
-    const material = new THREE.LineBasicMaterial({ vertexColors: true });
-
-    line = new THREE.Line(geometry, material);
-    line.scale.setScalar(0.05);
-    scene.add(line);
-
-    //
-
-    texture = new THREE.FramebufferTexture(textureSize, textureSize);
-
-    //
-
-    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
-    sprite = new THREE.Sprite(spriteMaterial);
-    sprite.scale.set(textureSize, textureSize, 1);
-    sceneOrtho.add(sprite);
-
-    updateSpritePosition();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const selection = document.getElementById('selection');
-    const controls = new OrbitControls(camera, selection);
-    controls.enablePan = false;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    cameraOrtho.left = -width / 2;
-    cameraOrtho.right = width / 2;
-    cameraOrtho.top = height / 2;
-    cameraOrtho.bottom = -height / 2;
-    cameraOrtho.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    updateSpritePosition();
-}
-
-function updateSpritePosition() {
-    const halfWidth = window.innerWidth / 2;
-    const halfHeight = window.innerHeight / 2;
-
-    const halfImageWidth = textureSize / 2;
-    const halfImageHeight = textureSize / 2;
-
-    sprite.position.set(-halfWidth + halfImageWidth, halfHeight - halfImageHeight, 1);
-}
-
-function animate() {
-    const colorAttribute = line.geometry.getAttribute('color');
-    updateColors(colorAttribute);
-
-    // scene rendering
-
-    renderer.clear();
-    renderer.render(scene, camera);
-
-    // calculate start position for copying data
-
-    vector.x = (window.innerWidth * dpr) / 2 - textureSize / 2;
-    vector.y = (window.innerHeight * dpr) / 2 - textureSize / 2;
-
-    renderer.copyFramebufferToTexture(texture, vector);
-
-    renderer.clearDepth();
-    renderer.render(sceneOrtho, cameraOrtho);
-}
-
-function updateColors(colorAttribute) {
-    const l = colorAttribute.count;
-
-    for (let i = 0; i < l; i++) {
-        const h = ((offset + i) % l) / l;
-
-        color.setHSL(h, 1, 0.5);
-        colorAttribute.setX(i, color.r);
-        colorAttribute.setY(i, color.g);
-        colorAttribute.setZ(i, color.b);
-    }
-
-    colorAttribute.needsUpdate = true;
-
-    offset -= 25;
-}
diff --git a/examples-testing/examples/webgl_furnace_test.ts b/examples-testing/examples/webgl_furnace_test.ts
deleted file mode 100644
index a81954176..000000000
--- a/examples-testing/examples/webgl_furnace_test.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as THREE from 'three';
-
-let scene, camera, renderer, radianceMap;
-
-const COLOR = 0xcccccc;
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-    const aspect = width / height;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(width, height);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    document.body.addEventListener('mouseover', function () {
-        scene.traverse(function (child) {
-            if (child.isMesh) child.material.color.setHex(0xffffff);
-        });
-
-        render();
-    });
-
-    document.body.addEventListener('mouseout', function () {
-        scene.traverse(function (child) {
-            if (child.isMesh) child.material.color.setHex(0xccccff); // tinted for visibility
-        });
-
-        render();
-    });
-
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(40, aspect, 1, 30);
-    camera.position.set(0, 0, 18);
-}
-
-function createObjects() {
-    const geometry = new THREE.SphereGeometry(0.4, 32, 16);
-
-    for (let x = 0; x <= 10; x++) {
-        for (let y = 0; y <= 10; y++) {
-            const material = new THREE.MeshPhysicalMaterial({
-                roughness: x / 10,
-                metalness: y / 10,
-                color: 0xffffff,
-                envMap: radianceMap,
-                envMapIntensity: 1,
-                transmission: 0,
-                ior: 1.5,
-            });
-
-            const mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = x - 5;
-            mesh.position.y = 5 - y;
-            scene.add(mesh);
-        }
-    }
-}
-
-function createEnvironment() {
-    const envScene = new THREE.Scene();
-    envScene.background = new THREE.Color(COLOR);
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    radianceMap = pmremGenerator.fromScene(envScene).texture;
-    pmremGenerator.dispose();
-
-    scene.background = envScene.background;
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-Promise.resolve().then(init).then(createEnvironment).then(createObjects).then(render);
diff --git a/examples-testing/examples/webgl_geometries.ts b/examples-testing/examples/webgl_geometries.ts
deleted file mode 100644
index 2b2d02613..000000000
--- a/examples-testing/examples/webgl_geometries.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.y = 400;
-
-    scene = new THREE.Scene();
-
-    let object;
-
-    const ambientLight = new THREE.AmbientLight(0xcccccc, 1.5);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 2.5, 0, 0);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    map.wrapS = map.wrapT = THREE.RepeatWrapping;
-    map.anisotropy = 16;
-    map.colorSpace = THREE.SRGBColorSpace;
-
-    const material = new THREE.MeshPhongMaterial({ map: map, side: THREE.DoubleSide });
-
-    //
-
-    object = new THREE.Mesh(new THREE.SphereGeometry(75, 20, 10), material);
-    object.position.set(-300, 0, 200);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.IcosahedronGeometry(75, 1), material);
-    object.position.set(-100, 0, 200);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.OctahedronGeometry(75, 2), material);
-    object.position.set(100, 0, 200);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.TetrahedronGeometry(75, 0), material);
-    object.position.set(300, 0, 200);
-    scene.add(object);
-
-    //
-
-    object = new THREE.Mesh(new THREE.PlaneGeometry(100, 100, 4, 4), material);
-    object.position.set(-300, 0, 0);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100, 4, 4, 4), material);
-    object.position.set(-100, 0, 0);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.CircleGeometry(50, 20, 0, Math.PI * 2), material);
-    object.position.set(100, 0, 0);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.RingGeometry(10, 50, 20, 5, 0, Math.PI * 2), material);
-    object.position.set(300, 0, 0);
-    scene.add(object);
-
-    //
-
-    object = new THREE.Mesh(new THREE.CylinderGeometry(25, 75, 100, 40, 5), material);
-    object.position.set(-300, 0, -200);
-    scene.add(object);
-
-    const points = [];
-
-    for (let i = 0; i < 50; i++) {
-        points.push(new THREE.Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
-    }
-
-    object = new THREE.Mesh(new THREE.LatheGeometry(points, 20), material);
-    object.position.set(-100, 0, -200);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.TorusGeometry(50, 20, 20, 20), material);
-    object.position.set(100, 0, -200);
-    scene.add(object);
-
-    object = new THREE.Mesh(new THREE.TorusKnotGeometry(50, 10, 50, 20), material);
-    object.position.set(300, 0, -200);
-    scene.add(object);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const timer = Date.now() * 0.0001;
-
-    camera.position.x = Math.cos(timer) * 800;
-    camera.position.z = Math.sin(timer) * 800;
-
-    camera.lookAt(scene.position);
-
-    scene.traverse(function (object) {
-        if (object.isMesh === true) {
-            object.rotation.x = timer * 5;
-            object.rotation.y = timer * 2.5;
-        }
-    });
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometries_parametric.ts b/examples-testing/examples/webgl_geometries_parametric.ts
deleted file mode 100644
index 29bf7ae26..000000000
--- a/examples-testing/examples/webgl_geometries_parametric.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import * as Curves from 'three/addons/curves/CurveExtras.js';
-import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
-import { ParametricGeometries } from 'three/addons/geometries/ParametricGeometries.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.y = 400;
-
-    scene = new THREE.Scene();
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xcccccc, 1.5);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 2.5, 0, 0);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    //
-
-    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    map.wrapS = map.wrapT = THREE.RepeatWrapping;
-    map.anisotropy = 16;
-    map.colorSpace = THREE.SRGBColorSpace;
-
-    const material = new THREE.MeshPhongMaterial({ map: map, side: THREE.DoubleSide });
-
-    //
-
-    let geometry, object;
-
-    geometry = new ParametricGeometry(ParametricGeometries.plane(100, 100), 10, 10);
-    geometry.center();
-    object = new THREE.Mesh(geometry, material);
-    object.position.set(-200, 0, 200);
-    scene.add(object);
-
-    geometry = new ParametricGeometry(ParametricGeometries.klein, 20, 20);
-    object = new THREE.Mesh(geometry, material);
-    object.position.set(0, 0, 200);
-    object.scale.multiplyScalar(5);
-    scene.add(object);
-
-    geometry = new ParametricGeometry(ParametricGeometries.mobius, 20, 20);
-    object = new THREE.Mesh(geometry, material);
-    object.position.set(200, 0, 200);
-    object.scale.multiplyScalar(30);
-    scene.add(object);
-
-    //
-
-    const GrannyKnot = new Curves.GrannyKnot();
-
-    const torus = new ParametricGeometries.TorusKnotGeometry(50, 10, 50, 20, 2, 3);
-    const sphere = new ParametricGeometries.SphereGeometry(50, 20, 10);
-    const tube = new ParametricGeometries.TubeGeometry(GrannyKnot, 100, 3, 8, true);
-
-    object = new THREE.Mesh(torus, material);
-    object.position.set(-200, 0, -200);
-    scene.add(object);
-
-    object = new THREE.Mesh(sphere, material);
-    object.position.set(0, 0, -200);
-    scene.add(object);
-
-    object = new THREE.Mesh(tube, material);
-    object.position.set(200, 0, -200);
-    object.scale.multiplyScalar(2);
-    scene.add(object);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const timer = Date.now() * 0.0001;
-
-    camera.position.x = Math.cos(timer) * 800;
-    camera.position.z = Math.sin(timer) * 800;
-
-    camera.lookAt(scene.position);
-
-    scene.traverse(function (object) {
-        if (object.isMesh === true) {
-            object.rotation.x = timer * 5;
-            object.rotation.y = timer * 2.5;
-        }
-    });
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_colors.ts b/examples-testing/examples/webgl_geometry_colors.ts
deleted file mode 100644
index bc0bf5174..000000000
--- a/examples-testing/examples/webgl_geometry_colors.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 1800;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 0, 1);
-    scene.add(light);
-
-    // shadow
-
-    const canvas = document.createElement('canvas');
-    canvas.width = 128;
-    canvas.height = 128;
-
-    const context = canvas.getContext('2d');
-    const gradient = context.createRadialGradient(
-        canvas.width / 2,
-        canvas.height / 2,
-        0,
-        canvas.width / 2,
-        canvas.height / 2,
-        canvas.width / 2,
-    );
-    gradient.addColorStop(0.1, 'rgba(210,210,210,1)');
-    gradient.addColorStop(1, 'rgba(255,255,255,1)');
-
-    context.fillStyle = gradient;
-    context.fillRect(0, 0, canvas.width, canvas.height);
-
-    const shadowTexture = new THREE.CanvasTexture(canvas);
-
-    const shadowMaterial = new THREE.MeshBasicMaterial({ map: shadowTexture });
-    const shadowGeo = new THREE.PlaneGeometry(300, 300, 1, 1);
-
-    let shadowMesh;
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.y = -250;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.y = -250;
-    shadowMesh.position.x = -400;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.y = -250;
-    shadowMesh.position.x = 400;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    const radius = 200;
-
-    const geometry1 = new THREE.IcosahedronGeometry(radius, 1);
-
-    const count = geometry1.attributes.position.count;
-    geometry1.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
-
-    const geometry2 = geometry1.clone();
-    const geometry3 = geometry1.clone();
-
-    const color = new THREE.Color();
-    const positions1 = geometry1.attributes.position;
-    const positions2 = geometry2.attributes.position;
-    const positions3 = geometry3.attributes.position;
-    const colors1 = geometry1.attributes.color;
-    const colors2 = geometry2.attributes.color;
-    const colors3 = geometry3.attributes.color;
-
-    for (let i = 0; i < count; i++) {
-        color.setHSL((positions1.getY(i) / radius + 1) / 2, 1.0, 0.5, THREE.SRGBColorSpace);
-        colors1.setXYZ(i, color.r, color.g, color.b);
-
-        color.setHSL(0, (positions2.getY(i) / radius + 1) / 2, 0.5, THREE.SRGBColorSpace);
-        colors2.setXYZ(i, color.r, color.g, color.b);
-
-        color.setRGB(1, 0.8 - (positions3.getY(i) / radius + 1) / 2, 0, THREE.SRGBColorSpace);
-        colors3.setXYZ(i, color.r, color.g, color.b);
-    }
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xffffff,
-        flatShading: true,
-        vertexColors: true,
-        shininess: 0,
-    });
-
-    const wireframeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, transparent: true });
-
-    let mesh = new THREE.Mesh(geometry1, material);
-    let wireframe = new THREE.Mesh(geometry1, wireframeMaterial);
-    mesh.add(wireframe);
-    mesh.position.x = -400;
-    mesh.rotation.x = -1.87;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry2, material);
-    wireframe = new THREE.Mesh(geometry2, wireframeMaterial);
-    mesh.add(wireframe);
-    mesh.position.x = 400;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry3, material);
-    wireframe = new THREE.Mesh(geometry3, wireframeMaterial);
-    mesh.add(wireframe);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_colors_lookuptable.ts b/examples-testing/examples/webgl_geometry_colors_lookuptable.ts
deleted file mode 100644
index 6b0138529..000000000
--- a/examples-testing/examples/webgl_geometry_colors_lookuptable.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Lut } from 'three/addons/math/Lut.js';
-
-let container;
-
-let perpCamera, orthoCamera, renderer, lut;
-
-let mesh, sprite;
-let scene, uiScene;
-
-let params;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    uiScene = new THREE.Scene();
-
-    lut = new Lut();
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    perpCamera = new THREE.PerspectiveCamera(60, width / height, 1, 100);
-    perpCamera.position.set(0, 0, 10);
-    scene.add(perpCamera);
-
-    orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 2);
-    orthoCamera.position.set(0.5, 0, 1);
-
-    sprite = new THREE.Sprite(
-        new THREE.SpriteMaterial({
-            map: new THREE.CanvasTexture(lut.createCanvas()),
-        }),
-    );
-    sprite.material.map.colorSpace = THREE.SRGBColorSpace;
-    sprite.scale.x = 0.125;
-    uiScene.add(sprite);
-
-    mesh = new THREE.Mesh(
-        undefined,
-        new THREE.MeshLambertMaterial({
-            side: THREE.DoubleSide,
-            color: 0xf5f5f5,
-            vertexColors: true,
-        }),
-    );
-    scene.add(mesh);
-
-    params = {
-        colorMap: 'rainbow',
-    };
-    loadModel();
-
-    const pointLight = new THREE.PointLight(0xffffff, 3, 0, 0);
-    perpCamera.add(pointLight);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.autoClear = false;
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const controls = new OrbitControls(perpCamera, renderer.domElement);
-    controls.addEventListener('change', render);
-
-    const gui = new GUI();
-
-    gui.add(params, 'colorMap', ['rainbow', 'cooltowarm', 'blackbody', 'grayscale']).onChange(function () {
-        updateColors();
-        render();
-    });
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    perpCamera.aspect = width / height;
-    perpCamera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    render();
-}
-
-function render() {
-    renderer.clear();
-    renderer.render(scene, perpCamera);
-    renderer.render(uiScene, orthoCamera);
-}
-
-function loadModel() {
-    const loader = new THREE.BufferGeometryLoader();
-    loader.load('models/json/pressure.json', function (geometry) {
-        geometry.center();
-        geometry.computeVertexNormals();
-
-        // default color attribute
-        const colors = [];
-
-        for (let i = 0, n = geometry.attributes.position.count; i < n; ++i) {
-            colors.push(1, 1, 1);
-        }
-
-        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-        mesh.geometry = geometry;
-        updateColors();
-
-        render();
-    });
-}
-
-function updateColors() {
-    lut.setColorMap(params.colorMap);
-
-    lut.setMax(2000);
-    lut.setMin(0);
-
-    const geometry = mesh.geometry;
-    const pressures = geometry.attributes.pressure;
-    const colors = geometry.attributes.color;
-    const color = new THREE.Color();
-
-    for (let i = 0; i < pressures.array.length; i++) {
-        const colorValue = pressures.array[i];
-
-        color.copy(lut.getColor(colorValue)).convertSRGBToLinear();
-
-        colors.setXYZ(i, color.r, color.g, color.b);
-    }
-
-    colors.needsUpdate = true;
-
-    const map = sprite.material.map;
-    lut.updateCanvas(map.image);
-    map.needsUpdate = true;
-}
diff --git a/examples-testing/examples/webgl_geometry_convex.ts b/examples-testing/examples/webgl_geometry_convex.ts
deleted file mode 100644
index ade9cb801..000000000
--- a/examples-testing/examples/webgl_geometry_convex.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js';
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let group, camera, scene, renderer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(15, 20, 30);
-    scene.add(camera);
-
-    // controls
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 20;
-    controls.maxDistance = 50;
-    controls.maxPolarAngle = Math.PI / 2;
-
-    // ambient light
-
-    scene.add(new THREE.AmbientLight(0x666666));
-
-    // point light
-
-    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
-    camera.add(light);
-
-    // helper
-
-    scene.add(new THREE.AxesHelper(20));
-
-    // textures
-
-    const loader = new THREE.TextureLoader();
-    const texture = loader.load('textures/sprites/disc.png');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    // points
-
-    let dodecahedronGeometry = new THREE.DodecahedronGeometry(10);
-
-    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
-
-    dodecahedronGeometry.deleteAttribute('normal');
-    dodecahedronGeometry.deleteAttribute('uv');
-
-    dodecahedronGeometry = BufferGeometryUtils.mergeVertices(dodecahedronGeometry);
-
-    const vertices = [];
-    const positionAttribute = dodecahedronGeometry.getAttribute('position');
-
-    for (let i = 0; i < positionAttribute.count; i++) {
-        const vertex = new THREE.Vector3();
-        vertex.fromBufferAttribute(positionAttribute, i);
-        vertices.push(vertex);
-    }
-
-    const pointsMaterial = new THREE.PointsMaterial({
-        color: 0x0080ff,
-        map: texture,
-        size: 1,
-        alphaTest: 0.5,
-    });
-
-    const pointsGeometry = new THREE.BufferGeometry().setFromPoints(vertices);
-
-    const points = new THREE.Points(pointsGeometry, pointsMaterial);
-    group.add(points);
-
-    // convex hull
-
-    const meshMaterial = new THREE.MeshLambertMaterial({
-        color: 0xffffff,
-        opacity: 0.5,
-        side: THREE.DoubleSide,
-        transparent: true,
-    });
-
-    const meshGeometry = new ConvexGeometry(vertices);
-
-    const mesh = new THREE.Mesh(meshGeometry, meshMaterial);
-    group.add(mesh);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    group.rotation.y += 0.005;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_cube.ts b/examples-testing/examples/webgl_geometry_cube.ts
deleted file mode 100644
index 572601acb..000000000
--- a/examples-testing/examples/webgl_geometry_cube.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let mesh;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-
-    const texture = new THREE.TextureLoader().load('textures/crate.gif');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const geometry = new THREE.BoxGeometry();
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.rotation.x += 0.005;
-    mesh.rotation.y += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_dynamic.ts b/examples-testing/examples/webgl_geometry_dynamic.ts
deleted file mode 100644
index 06e858f54..000000000
--- a/examples-testing/examples/webgl_geometry_dynamic.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-
-let camera, controls, scene, renderer, stats;
-
-let mesh, geometry, material, clock;
-
-const worldWidth = 128,
-    worldDepth = 128;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
-    camera.position.y = 200;
-
-    clock = new THREE.Clock();
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xaaccff);
-    scene.fog = new THREE.FogExp2(0xaaccff, 0.0007);
-
-    geometry = new THREE.PlaneGeometry(20000, 20000, worldWidth - 1, worldDepth - 1);
-    geometry.rotateX(-Math.PI / 2);
-
-    const position = geometry.attributes.position;
-    position.usage = THREE.DynamicDrawUsage;
-
-    for (let i = 0; i < position.count; i++) {
-        const y = 35 * Math.sin(i / 2);
-        position.setY(i, y);
-    }
-
-    const texture = new THREE.TextureLoader().load('textures/water.jpg');
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    texture.repeat.set(5, 5);
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    material = new THREE.MeshBasicMaterial({ color: 0x0044ff, map: texture });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-
-    controls.movementSpeed = 500;
-    controls.lookSpeed = 0.1;
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-    const time = clock.getElapsedTime() * 10;
-
-    const position = geometry.attributes.position;
-
-    for (let i = 0; i < position.count; i++) {
-        const y = 35 * Math.sin(i / 5 + (time + i) / 7);
-        position.setY(i, y);
-    }
-
-    position.needsUpdate = true;
-
-    controls.update(delta);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_extrude_shapes.ts b/examples-testing/examples/webgl_geometry_extrude_shapes.ts
deleted file mode 100644
index 7428aee31..000000000
--- a/examples-testing/examples/webgl_geometry_extrude_shapes.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-
-let camera, scene, renderer, controls;
-
-init();
-
-function init() {
-    const info = document.createElement('div');
-    info.style.position = 'absolute';
-    info.style.top = '10px';
-    info.style.width = '100%';
-    info.style.textAlign = 'center';
-    info.style.color = '#fff';
-    info.style.link = '#f80';
-    info.innerHTML =
-        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - geometry extrude shapes';
-    document.body.appendChild(info);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x222222);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 500);
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.minDistance = 200;
-    controls.maxDistance = 500;
-
-    scene.add(new THREE.AmbientLight(0x666666));
-
-    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
-    light.position.copy(camera.position);
-    scene.add(light);
-
-    //
-
-    const closedSpline = new THREE.CatmullRomCurve3([
-        new THREE.Vector3(-60, -100, 60),
-        new THREE.Vector3(-60, 20, 60),
-        new THREE.Vector3(-60, 120, 60),
-        new THREE.Vector3(60, 20, -60),
-        new THREE.Vector3(60, -100, -60),
-    ]);
-
-    closedSpline.curveType = 'catmullrom';
-    closedSpline.closed = true;
-
-    const extrudeSettings1 = {
-        steps: 100,
-        bevelEnabled: false,
-        extrudePath: closedSpline,
-    };
-
-    const pts1 = [],
-        count = 3;
-
-    for (let i = 0; i < count; i++) {
-        const l = 20;
-
-        const a = ((2 * i) / count) * Math.PI;
-
-        pts1.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
-    }
-
-    const shape1 = new THREE.Shape(pts1);
-
-    const geometry1 = new THREE.ExtrudeGeometry(shape1, extrudeSettings1);
-
-    const material1 = new THREE.MeshLambertMaterial({ color: 0xb00000, wireframe: false });
-
-    const mesh1 = new THREE.Mesh(geometry1, material1);
-
-    scene.add(mesh1);
-
-    //
-
-    const randomPoints = [];
-
-    for (let i = 0; i < 10; i++) {
-        randomPoints.push(
-            new THREE.Vector3((i - 4.5) * 50, THREE.MathUtils.randFloat(-50, 50), THREE.MathUtils.randFloat(-50, 50)),
-        );
-    }
-
-    const randomSpline = new THREE.CatmullRomCurve3(randomPoints);
-
-    //
-
-    const extrudeSettings2 = {
-        steps: 200,
-        bevelEnabled: false,
-        extrudePath: randomSpline,
-    };
-
-    const pts2 = [],
-        numPts = 5;
-
-    for (let i = 0; i < numPts * 2; i++) {
-        const l = i % 2 == 1 ? 10 : 20;
-
-        const a = (i / numPts) * Math.PI;
-
-        pts2.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
-    }
-
-    const shape2 = new THREE.Shape(pts2);
-
-    const geometry2 = new THREE.ExtrudeGeometry(shape2, extrudeSettings2);
-
-    const material2 = new THREE.MeshLambertMaterial({ color: 0xff8000, wireframe: false });
-
-    const mesh2 = new THREE.Mesh(geometry2, material2);
-
-    scene.add(mesh2);
-
-    //
-
-    const materials = [material1, material2];
-
-    const extrudeSettings3 = {
-        depth: 20,
-        steps: 1,
-        bevelEnabled: true,
-        bevelThickness: 2,
-        bevelSize: 4,
-        bevelSegments: 1,
-    };
-
-    const geometry3 = new THREE.ExtrudeGeometry(shape2, extrudeSettings3);
-
-    const mesh3 = new THREE.Mesh(geometry3, materials);
-
-    mesh3.position.set(50, 100, 50);
-
-    scene.add(mesh3);
-}
-
-function animate() {
-    controls.update();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_extrude_splines.ts b/examples-testing/examples/webgl_geometry_extrude_splines.ts
deleted file mode 100644
index 0741083da..000000000
--- a/examples-testing/examples/webgl_geometry_extrude_splines.ts
+++ /dev/null
@@ -1,310 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import * as Curves from 'three/addons/curves/CurveExtras.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, stats;
-
-let camera, scene, renderer, splineCamera, cameraHelper, cameraEye;
-
-const direction = new THREE.Vector3();
-const binormal = new THREE.Vector3();
-const normal = new THREE.Vector3();
-const position = new THREE.Vector3();
-const lookAt = new THREE.Vector3();
-
-const pipeSpline = new THREE.CatmullRomCurve3([
-    new THREE.Vector3(0, 10, -10),
-    new THREE.Vector3(10, 0, -10),
-    new THREE.Vector3(20, 0, 0),
-    new THREE.Vector3(30, 0, 10),
-    new THREE.Vector3(30, 0, 20),
-    new THREE.Vector3(20, 0, 30),
-    new THREE.Vector3(10, 0, 30),
-    new THREE.Vector3(0, 0, 30),
-    new THREE.Vector3(-10, 10, 30),
-    new THREE.Vector3(-10, 20, 30),
-    new THREE.Vector3(0, 30, 30),
-    new THREE.Vector3(10, 30, 30),
-    new THREE.Vector3(20, 30, 15),
-    new THREE.Vector3(10, 30, 10),
-    new THREE.Vector3(0, 30, 10),
-    new THREE.Vector3(-10, 20, 10),
-    new THREE.Vector3(-10, 10, 10),
-    new THREE.Vector3(0, 0, 10),
-    new THREE.Vector3(10, -10, 10),
-    new THREE.Vector3(20, -15, 10),
-    new THREE.Vector3(30, -15, 10),
-    new THREE.Vector3(40, -15, 10),
-    new THREE.Vector3(50, -15, 10),
-    new THREE.Vector3(60, 0, 10),
-    new THREE.Vector3(70, 0, 0),
-    new THREE.Vector3(80, 0, 0),
-    new THREE.Vector3(90, 0, 0),
-    new THREE.Vector3(100, 0, 0),
-]);
-
-const sampleClosedSpline = new THREE.CatmullRomCurve3([
-    new THREE.Vector3(0, -40, -40),
-    new THREE.Vector3(0, 40, -40),
-    new THREE.Vector3(0, 140, -40),
-    new THREE.Vector3(0, 40, 40),
-    new THREE.Vector3(0, -40, 40),
-]);
-
-sampleClosedSpline.curveType = 'catmullrom';
-sampleClosedSpline.closed = true;
-
-// Keep a dictionary of Curve instances
-const splines = {
-    GrannyKnot: new Curves.GrannyKnot(),
-    HeartCurve: new Curves.HeartCurve(3.5),
-    VivianiCurve: new Curves.VivianiCurve(70),
-    KnotCurve: new Curves.KnotCurve(),
-    HelixCurve: new Curves.HelixCurve(),
-    TrefoilKnot: new Curves.TrefoilKnot(),
-    TorusKnot: new Curves.TorusKnot(20),
-    CinquefoilKnot: new Curves.CinquefoilKnot(20),
-    TrefoilPolynomialKnot: new Curves.TrefoilPolynomialKnot(14),
-    FigureEightPolynomialKnot: new Curves.FigureEightPolynomialKnot(),
-    DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
-    DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
-    DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
-    DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
-    PipeSpline: pipeSpline,
-    SampleClosedSpline: sampleClosedSpline,
-};
-
-let parent, tubeGeometry, mesh;
-
-const params = {
-    spline: 'GrannyKnot',
-    scale: 4,
-    extrusionSegments: 100,
-    radiusSegments: 3,
-    closed: true,
-    animationView: false,
-    lookAhead: false,
-    cameraHelper: false,
-};
-
-const material = new THREE.MeshLambertMaterial({ color: 0xff00ff });
-
-const wireframeMaterial = new THREE.MeshBasicMaterial({
-    color: 0x000000,
-    opacity: 0.3,
-    wireframe: true,
-    transparent: true,
-});
-
-function addTube() {
-    if (mesh !== undefined) {
-        parent.remove(mesh);
-        mesh.geometry.dispose();
-    }
-
-    const extrudePath = splines[params.spline];
-
-    tubeGeometry = new THREE.TubeGeometry(
-        extrudePath,
-        params.extrusionSegments,
-        2,
-        params.radiusSegments,
-        params.closed,
-    );
-
-    addGeometry(tubeGeometry);
-
-    setScale();
-}
-
-function setScale() {
-    mesh.scale.set(params.scale, params.scale, params.scale);
-}
-
-function addGeometry(geometry) {
-    // 3D shape
-
-    mesh = new THREE.Mesh(geometry, material);
-    const wireframe = new THREE.Mesh(geometry, wireframeMaterial);
-    mesh.add(wireframe);
-
-    parent.add(mesh);
-}
-
-function animateCamera() {
-    cameraHelper.visible = params.cameraHelper;
-    cameraEye.visible = params.cameraHelper;
-}
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
-    camera.position.set(0, 50, 500);
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    // light
-
-    scene.add(new THREE.AmbientLight(0xffffff));
-
-    const light = new THREE.DirectionalLight(0xffffff, 1.5);
-    light.position.set(0, 0, 1);
-    scene.add(light);
-
-    // tube
-
-    parent = new THREE.Object3D();
-    scene.add(parent);
-
-    splineCamera = new THREE.PerspectiveCamera(84, window.innerWidth / window.innerHeight, 0.01, 1000);
-    parent.add(splineCamera);
-
-    cameraHelper = new THREE.CameraHelper(splineCamera);
-    scene.add(cameraHelper);
-
-    addTube();
-
-    // debug camera
-
-    cameraEye = new THREE.Mesh(new THREE.SphereGeometry(5), new THREE.MeshBasicMaterial({ color: 0xdddddd }));
-    parent.add(cameraEye);
-
-    cameraHelper.visible = params.cameraHelper;
-    cameraEye.visible = params.cameraHelper;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // stats
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // dat.GUI
-
-    const gui = new GUI({ width: 285 });
-
-    const folderGeometry = gui.addFolder('Geometry');
-    folderGeometry.add(params, 'spline', Object.keys(splines)).onChange(function () {
-        addTube();
-    });
-    folderGeometry
-        .add(params, 'scale', 2, 10)
-        .step(2)
-        .onChange(function () {
-            setScale();
-        });
-    folderGeometry
-        .add(params, 'extrusionSegments', 50, 500)
-        .step(50)
-        .onChange(function () {
-            addTube();
-        });
-    folderGeometry
-        .add(params, 'radiusSegments', 2, 12)
-        .step(1)
-        .onChange(function () {
-            addTube();
-        });
-    folderGeometry.add(params, 'closed').onChange(function () {
-        addTube();
-    });
-    folderGeometry.open();
-
-    const folderCamera = gui.addFolder('Camera');
-    folderCamera.add(params, 'animationView').onChange(function () {
-        animateCamera();
-    });
-    folderCamera.add(params, 'lookAhead').onChange(function () {
-        animateCamera();
-    });
-    folderCamera.add(params, 'cameraHelper').onChange(function () {
-        animateCamera();
-    });
-    folderCamera.open();
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 100;
-    controls.maxDistance = 2000;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    // animate camera along spline
-
-    const time = Date.now();
-    const looptime = 20 * 1000;
-    const t = (time % looptime) / looptime;
-
-    tubeGeometry.parameters.path.getPointAt(t, position);
-    position.multiplyScalar(params.scale);
-
-    // interpolation
-
-    const segments = tubeGeometry.tangents.length;
-    const pickt = t * segments;
-    const pick = Math.floor(pickt);
-    const pickNext = (pick + 1) % segments;
-
-    binormal.subVectors(tubeGeometry.binormals[pickNext], tubeGeometry.binormals[pick]);
-    binormal.multiplyScalar(pickt - pick).add(tubeGeometry.binormals[pick]);
-
-    tubeGeometry.parameters.path.getTangentAt(t, direction);
-    const offset = 15;
-
-    normal.copy(binormal).cross(direction);
-
-    // we move on a offset on its binormal
-
-    position.add(normal.clone().multiplyScalar(offset));
-
-    splineCamera.position.copy(position);
-    cameraEye.position.copy(position);
-
-    // using arclength for stablization in look ahead
-
-    tubeGeometry.parameters.path.getPointAt((t + 30 / tubeGeometry.parameters.path.getLength()) % 1, lookAt);
-    lookAt.multiplyScalar(params.scale);
-
-    // camera orientation 2 - up orientation via normal
-
-    if (!params.lookAhead) lookAt.copy(position).add(direction);
-    splineCamera.matrix.lookAt(splineCamera.position, lookAt, normal);
-    splineCamera.quaternion.setFromRotationMatrix(splineCamera.matrix);
-
-    cameraHelper.update();
-
-    renderer.render(scene, params.animationView === true ? splineCamera : camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_minecraft.ts b/examples-testing/examples/webgl_geometry_minecraft.ts
deleted file mode 100644
index 765aa1e49..000000000
--- a/examples-testing/examples/webgl_geometry_minecraft.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let container, stats;
-
-let camera, controls, scene, renderer;
-
-const worldWidth = 128,
-    worldDepth = 128;
-const worldHalfWidth = worldWidth / 2;
-const worldHalfDepth = worldDepth / 2;
-const data = generateHeight(worldWidth, worldDepth);
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
-    camera.position.y = getY(worldHalfWidth, worldHalfDepth) * 100 + 100;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xbfd1e5);
-
-    // sides
-
-    const matrix = new THREE.Matrix4();
-
-    const pxGeometry = new THREE.PlaneGeometry(100, 100);
-    pxGeometry.attributes.uv.array[1] = 0.5;
-    pxGeometry.attributes.uv.array[3] = 0.5;
-    pxGeometry.rotateY(Math.PI / 2);
-    pxGeometry.translate(50, 0, 0);
-
-    const nxGeometry = new THREE.PlaneGeometry(100, 100);
-    nxGeometry.attributes.uv.array[1] = 0.5;
-    nxGeometry.attributes.uv.array[3] = 0.5;
-    nxGeometry.rotateY(-Math.PI / 2);
-    nxGeometry.translate(-50, 0, 0);
-
-    const pyGeometry = new THREE.PlaneGeometry(100, 100);
-    pyGeometry.attributes.uv.array[5] = 0.5;
-    pyGeometry.attributes.uv.array[7] = 0.5;
-    pyGeometry.rotateX(-Math.PI / 2);
-    pyGeometry.translate(0, 50, 0);
-
-    const pzGeometry = new THREE.PlaneGeometry(100, 100);
-    pzGeometry.attributes.uv.array[1] = 0.5;
-    pzGeometry.attributes.uv.array[3] = 0.5;
-    pzGeometry.translate(0, 0, 50);
-
-    const nzGeometry = new THREE.PlaneGeometry(100, 100);
-    nzGeometry.attributes.uv.array[1] = 0.5;
-    nzGeometry.attributes.uv.array[3] = 0.5;
-    nzGeometry.rotateY(Math.PI);
-    nzGeometry.translate(0, 0, -50);
-
-    //
-
-    const geometries = [];
-
-    for (let z = 0; z < worldDepth; z++) {
-        for (let x = 0; x < worldWidth; x++) {
-            const h = getY(x, z);
-
-            matrix.makeTranslation(x * 100 - worldHalfWidth * 100, h * 100, z * 100 - worldHalfDepth * 100);
-
-            const px = getY(x + 1, z);
-            const nx = getY(x - 1, z);
-            const pz = getY(x, z + 1);
-            const nz = getY(x, z - 1);
-
-            geometries.push(pyGeometry.clone().applyMatrix4(matrix));
-
-            if ((px !== h && px !== h + 1) || x === 0) {
-                geometries.push(pxGeometry.clone().applyMatrix4(matrix));
-            }
-
-            if ((nx !== h && nx !== h + 1) || x === worldWidth - 1) {
-                geometries.push(nxGeometry.clone().applyMatrix4(matrix));
-            }
-
-            if ((pz !== h && pz !== h + 1) || z === worldDepth - 1) {
-                geometries.push(pzGeometry.clone().applyMatrix4(matrix));
-            }
-
-            if ((nz !== h && nz !== h + 1) || z === 0) {
-                geometries.push(nzGeometry.clone().applyMatrix4(matrix));
-            }
-        }
-    }
-
-    const geometry = BufferGeometryUtils.mergeGeometries(geometries);
-    geometry.computeBoundingSphere();
-
-    const texture = new THREE.TextureLoader().load('textures/minecraft/atlas.png');
-    texture.colorSpace = THREE.SRGBColorSpace;
-    texture.magFilter = THREE.NearestFilter;
-
-    const mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ map: texture, side: THREE.DoubleSide }));
-    scene.add(mesh);
-
-    const ambientLight = new THREE.AmbientLight(0xeeeeee, 3);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 12);
-    directionalLight.position.set(1, 1, 0.5).normalize();
-    scene.add(directionalLight);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-
-    controls.movementSpeed = 1000;
-    controls.lookSpeed = 0.125;
-    controls.lookVertical = true;
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-function generateHeight(width, height) {
-    const data = [],
-        perlin = new ImprovedNoise(),
-        size = width * height,
-        z = Math.random() * 100;
-
-    let quality = 2;
-
-    for (let j = 0; j < 4; j++) {
-        if (j === 0) for (let i = 0; i < size; i++) data[i] = 0;
-
-        for (let i = 0; i < size; i++) {
-            const x = i % width,
-                y = (i / width) | 0;
-            data[i] += perlin.noise(x / quality, y / quality, z) * quality;
-        }
-
-        quality *= 4;
-    }
-
-    return data;
-}
-
-function getY(x, z) {
-    return (data[x + z * worldWidth] * 0.15) | 0;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    controls.update(clock.getDelta());
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_nurbs.ts b/examples-testing/examples/webgl_geometry_nurbs.ts
deleted file mode 100644
index a603710bd..000000000
--- a/examples-testing/examples/webgl_geometry_nurbs.ts
+++ /dev/null
@@ -1,298 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
-import { NURBSSurface } from 'three/addons/curves/NURBSSurface.js';
-import { NURBSVolume } from 'three/addons/curves/NURBSVolume.js';
-import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-let group;
-
-let targetRotation = 0;
-let targetRotationOnPointerDown = 0;
-
-let pointerX = 0;
-let pointerXOnPointerDown = 0;
-
-let windowHalfX = window.innerWidth / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(0, 150, 750);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    scene.add(new THREE.AmbientLight(0xffffff));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    group = new THREE.Group();
-    group.position.y = 50;
-    scene.add(group);
-
-    // NURBS curve
-
-    const nurbsControlPoints = [];
-    const nurbsKnots = [];
-    const nurbsDegree = 3;
-
-    for (let i = 0; i <= nurbsDegree; i++) {
-        nurbsKnots.push(0);
-    }
-
-    for (let i = 0, j = 20; i < j; i++) {
-        nurbsControlPoints.push(
-            new THREE.Vector4(
-                Math.random() * 400 - 200,
-                Math.random() * 400,
-                Math.random() * 400 - 200,
-                1, // weight of control point: higher means stronger attraction
-            ),
-        );
-
-        const knot = (i + 1) / (j - nurbsDegree);
-        nurbsKnots.push(THREE.MathUtils.clamp(knot, 0, 1));
-    }
-
-    const nurbsCurve = new NURBSCurve(nurbsDegree, nurbsKnots, nurbsControlPoints);
-
-    const nurbsGeometry = new THREE.BufferGeometry();
-    nurbsGeometry.setFromPoints(nurbsCurve.getPoints(200));
-
-    const nurbsMaterial = new THREE.LineBasicMaterial({ color: 0x333333 });
-
-    const nurbsLine = new THREE.Line(nurbsGeometry, nurbsMaterial);
-    nurbsLine.position.set(0, -100, 0);
-    group.add(nurbsLine);
-
-    const nurbsControlPointsGeometry = new THREE.BufferGeometry();
-    nurbsControlPointsGeometry.setFromPoints(nurbsCurve.controlPoints);
-
-    const nurbsControlPointsMaterial = new THREE.LineBasicMaterial({
-        color: 0x333333,
-        opacity: 0.25,
-        transparent: true,
-    });
-
-    const nurbsControlPointsLine = new THREE.Line(nurbsControlPointsGeometry, nurbsControlPointsMaterial);
-    nurbsControlPointsLine.position.copy(nurbsLine.position);
-    group.add(nurbsControlPointsLine);
-
-    // NURBS surface
-    {
-        const nsControlPoints = [
-            [
-                new THREE.Vector4(-200, -200, 100, 1),
-                new THREE.Vector4(-200, -100, -200, 1),
-                new THREE.Vector4(-200, 100, 250, 1),
-                new THREE.Vector4(-200, 200, -100, 1),
-            ],
-            [
-                new THREE.Vector4(0, -200, 0, 1),
-                new THREE.Vector4(0, -100, -100, 5),
-                new THREE.Vector4(0, 100, 150, 5),
-                new THREE.Vector4(0, 200, 0, 1),
-            ],
-            [
-                new THREE.Vector4(200, -200, -100, 1),
-                new THREE.Vector4(200, -100, 200, 1),
-                new THREE.Vector4(200, 100, -250, 1),
-                new THREE.Vector4(200, 200, 100, 1),
-            ],
-        ];
-        const degree1 = 2;
-        const degree2 = 3;
-        const knots1 = [0, 0, 0, 1, 1, 1];
-        const knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
-        const nurbsSurface = new NURBSSurface(degree1, degree2, knots1, knots2, nsControlPoints);
-
-        const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-        map.wrapS = map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 16;
-        map.colorSpace = THREE.SRGBColorSpace;
-
-        function getSurfacePoint(u, v, target) {
-            return nurbsSurface.getPoint(u, v, target);
-        }
-
-        const geometry = new ParametricGeometry(getSurfacePoint, 20, 20);
-        const material = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const object = new THREE.Mesh(geometry, material);
-        object.position.set(-400, 100, 0);
-        object.scale.multiplyScalar(1);
-        group.add(object);
-    }
-
-    // NURBS volume
-    {
-        const nsControlPoints = [
-            [
-                [new THREE.Vector4(-200, -200, -200, 1), new THREE.Vector4(-200, -200, 200, 1)],
-                [new THREE.Vector4(-200, -100, -200, 1), new THREE.Vector4(-200, -100, 200, 1)],
-                [new THREE.Vector4(-200, 100, -200, 1), new THREE.Vector4(-200, 100, 200, 1)],
-                [new THREE.Vector4(-200, 200, -200, 1), new THREE.Vector4(-200, 200, 200, 1)],
-            ],
-            [
-                [new THREE.Vector4(0, -200, -200, 1), new THREE.Vector4(0, -200, 200, 1)],
-                [new THREE.Vector4(0, -100, -200, 1), new THREE.Vector4(0, -100, 200, 1)],
-                [new THREE.Vector4(0, 100, -200, 1), new THREE.Vector4(0, 100, 200, 1)],
-                [new THREE.Vector4(0, 200, -200, 1), new THREE.Vector4(0, 200, 200, 1)],
-            ],
-            [
-                [new THREE.Vector4(200, -200, -200, 1), new THREE.Vector4(200, -200, 200, 1)],
-                [new THREE.Vector4(200, -100, 0, 1), new THREE.Vector4(200, -100, 100, 1)],
-                [new THREE.Vector4(200, 100, 0, 1), new THREE.Vector4(200, 100, 100, 1)],
-                [new THREE.Vector4(200, 200, 0, 1), new THREE.Vector4(200, 200, 100, 1)],
-            ],
-        ];
-        const degree1 = 2;
-        const degree2 = 3;
-        const degree3 = 1;
-        const knots1 = [0, 0, 0, 1, 1, 1];
-        const knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
-        const knots3 = [0, 0, 1, 1];
-        const nurbsVolume = new NURBSVolume(degree1, degree2, degree3, knots1, knots2, knots3, nsControlPoints);
-
-        const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-        map.wrapS = map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 16;
-        map.colorSpace = THREE.SRGBColorSpace;
-
-        // Since ParametricGeometry() only support bi-variate parametric geometries
-        // we create evaluation functions for different surfaces with one of the three
-        // parameter values (u, v, w) kept constant and create multiple THREE.Mesh
-        // objects one for each surface
-        function getSurfacePointFront(u, v, target) {
-            return nurbsVolume.getPoint(u, v, 0, target);
-        }
-
-        function getSurfacePointMiddle(u, v, target) {
-            return nurbsVolume.getPoint(u, v, 0.5, target);
-        }
-
-        function getSurfacePointBack(u, v, target) {
-            return nurbsVolume.getPoint(u, v, 1, target);
-        }
-
-        function getSurfacePointTop(u, w, target) {
-            return nurbsVolume.getPoint(u, 1, w, target);
-        }
-
-        function getSurfacePointSide(v, w, target) {
-            return nurbsVolume.getPoint(0, v, w, target);
-        }
-
-        const geometryFront = new ParametricGeometry(getSurfacePointFront, 20, 20);
-        const materialFront = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const objectFront = new THREE.Mesh(geometryFront, materialFront);
-        objectFront.position.set(400, 100, 0);
-        objectFront.scale.multiplyScalar(0.5);
-        group.add(objectFront);
-
-        const geometryMiddle = new ParametricGeometry(getSurfacePointMiddle, 20, 20);
-        const materialMiddle = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const objectMiddle = new THREE.Mesh(geometryMiddle, materialMiddle);
-        objectMiddle.position.set(400, 100, 0);
-        objectMiddle.scale.multiplyScalar(0.5);
-        group.add(objectMiddle);
-
-        const geometryBack = new ParametricGeometry(getSurfacePointBack, 20, 20);
-        const materialBack = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const objectBack = new THREE.Mesh(geometryBack, materialBack);
-        objectBack.position.set(400, 100, 0);
-        objectBack.scale.multiplyScalar(0.5);
-        group.add(objectBack);
-
-        const geometryTop = new ParametricGeometry(getSurfacePointTop, 20, 20);
-        const materialTop = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const objectTop = new THREE.Mesh(geometryTop, materialTop);
-        objectTop.position.set(400, 100, 0);
-        objectTop.scale.multiplyScalar(0.5);
-        group.add(objectTop);
-
-        const geometrySide = new ParametricGeometry(getSurfacePointSide, 20, 20);
-        const materialSide = new THREE.MeshLambertMaterial({ map: map, side: THREE.DoubleSide });
-        const objectSide = new THREE.Mesh(geometrySide, materialSide);
-        objectSide.position.set(400, 100, 0);
-        objectSide.scale.multiplyScalar(0.5);
-        group.add(objectSide);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointerdown', onPointerDown);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onPointerDown(event) {
-    if (event.isPrimary === false) return;
-
-    pointerXOnPointerDown = event.clientX - windowHalfX;
-    targetRotationOnPointerDown = targetRotation;
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    pointerX = event.clientX - windowHalfX;
-
-    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
-}
-
-function onPointerUp() {
-    if (event.isPrimary === false) return;
-
-    document.removeEventListener('pointermove', onPointerMove);
-    document.removeEventListener('pointerup', onPointerUp);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_sdf.ts b/examples-testing/examples/webgl_geometry_sdf.ts
deleted file mode 100644
index fa5dba102..000000000
--- a/examples-testing/examples/webgl_geometry_sdf.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { SDFGeometryGenerator } from 'three/addons/geometries/SDFGeometryGenerator.js';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let renderer, stats, meshFromSDF, scene, camera, clock, controls;
-
-const settings = {
-    res: 4,
-    bounds: 1,
-    autoRotate: true,
-    wireframe: true,
-    material: 'depth',
-    vertexCount: '0',
-};
-
-// Example SDF from https://www.shadertoy.com/view/MdXSWn -->
-
-const shader = /* glsl */ `
-				float dist(vec3 p) {
-					p.xyz = p.xzy;
-					p *= 1.2;
-					vec3 z = p;
-					vec3 dz=vec3(0.0);
-					float power = 8.0;
-					float r, theta, phi;
-					float dr = 1.0;
-					
-					float t0 = 1.0;
-					for(int i = 0; i < 7; ++i) {
-						r = length(z);
-						if(r > 2.0) continue;
-						theta = atan(z.y / z.x);
-						#ifdef phase_shift_on
-						phi = asin(z.z / r) ;
-						#else
-						phi = asin(z.z / r);
-						#endif
-						
-						dr = pow(r, power - 1.0) * dr * power + 1.0;
-					
-						r = pow(r, power);
-						theta = theta * power;
-						phi = phi * power;
-						
-						z = r * vec3(cos(theta)*cos(phi), sin(theta)*cos(phi), sin(phi)) + p;
-						
-						t0 = min(t0, r);
-					}
-		
-					return 0.5 * log(r) * r / dr;
-				}
-			`;
-
-init();
-
-function init() {
-    const w = window.innerWidth;
-    const h = window.innerHeight;
-
-    camera = new THREE.OrthographicCamera(w / -2, w / 2, h / 2, h / -2, 0.01, 1600);
-    camera.position.z = 1100;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const panel = new GUI();
-
-    panel.add(settings, 'res', 1, 6, 1).name('Res').onFinishChange(compile);
-    panel.add(settings, 'bounds', 1, 10, 1).name('Bounds').onFinishChange(compile);
-    panel.add(settings, 'material', ['depth', 'normal']).name('Material').onChange(setMaterial);
-    panel.add(settings, 'wireframe').name('Wireframe').onChange(setMaterial);
-    panel.add(settings, 'autoRotate').name('Auto Rotate');
-    panel.add(settings, 'vertexCount').name('Vertex count').listen().disable();
-
-    //
-
-    compile();
-}
-
-function compile() {
-    const generator = new SDFGeometryGenerator(renderer);
-    const geometry = generator.generate(Math.pow(2, settings.res + 2), shader, settings.bounds);
-    geometry.computeVertexNormals();
-
-    if (meshFromSDF) {
-        // updates mesh
-
-        meshFromSDF.geometry.dispose();
-        meshFromSDF.geometry = geometry;
-    } else {
-        // inits meshFromSDF : THREE.Mesh
-
-        meshFromSDF = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial());
-        scene.add(meshFromSDF);
-
-        const scale = (Math.min(window.innerWidth, window.innerHeight) / 2) * 0.66;
-        meshFromSDF.scale.set(scale, scale, scale);
-
-        setMaterial();
-    }
-
-    settings.vertexCount = geometry.attributes.position.count;
-}
-
-function setMaterial() {
-    meshFromSDF.material.dispose();
-
-    if (settings.material == 'depth') {
-        meshFromSDF.material = new THREE.MeshDepthMaterial();
-    } else if (settings.material == 'normal') {
-        meshFromSDF.material = new THREE.MeshNormalMaterial();
-    }
-
-    meshFromSDF.material.wireframe = settings.wireframe;
-}
-
-function onWindowResize() {
-    const w = window.innerWidth;
-    const h = window.innerHeight;
-
-    renderer.setSize(w, h);
-
-    camera.left = w / -2;
-    camera.right = w / 2;
-    camera.top = h / 2;
-    camera.bottom = h / -2;
-
-    camera.updateProjectionMatrix();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-function animate() {
-    controls.update();
-
-    if (settings.autoRotate) {
-        meshFromSDF.rotation.y += Math.PI * 0.05 * clock.getDelta();
-    }
-
-    render();
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_geometry_shapes.ts b/examples-testing/examples/webgl_geometry_shapes.ts
deleted file mode 100644
index f1d00f011..000000000
--- a/examples-testing/examples/webgl_geometry_shapes.ts
+++ /dev/null
@@ -1,363 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let group;
-
-let targetRotation = 0;
-let targetRotationOnPointerDown = 0;
-
-let pointerX = 0;
-let pointerXOnPointerDown = 0;
-
-let windowHalfX = window.innerWidth / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 150, 500);
-    scene.add(camera);
-
-    const light = new THREE.PointLight(0xffffff, 2.5, 0, 0);
-    camera.add(light);
-
-    group = new THREE.Group();
-    group.position.y = 50;
-    scene.add(group);
-
-    const loader = new THREE.TextureLoader();
-    const texture = loader.load('textures/uv_grid_opengl.jpg');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    // it's necessary to apply these settings in order to correctly display the texture on a shape geometry
-
-    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-    texture.repeat.set(0.008, 0.008);
-
-    function addShape(shape, extrudeSettings, color, x, y, z, rx, ry, rz, s) {
-        // flat shape with texture
-        // note: default UVs generated by THREE.ShapeGeometry are simply the x- and y-coordinates of the vertices
-
-        let geometry = new THREE.ShapeGeometry(shape);
-
-        let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ side: THREE.DoubleSide, map: texture }));
-        mesh.position.set(x, y, z - 175);
-        mesh.rotation.set(rx, ry, rz);
-        mesh.scale.set(s, s, s);
-        group.add(mesh);
-
-        // flat shape
-
-        geometry = new THREE.ShapeGeometry(shape);
-
-        mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: color, side: THREE.DoubleSide }));
-        mesh.position.set(x, y, z - 125);
-        mesh.rotation.set(rx, ry, rz);
-        mesh.scale.set(s, s, s);
-        group.add(mesh);
-
-        // extruded shape
-
-        geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
-
-        mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({ color: color }));
-        mesh.position.set(x, y, z - 75);
-        mesh.rotation.set(rx, ry, rz);
-        mesh.scale.set(s, s, s);
-        group.add(mesh);
-
-        addLineShape(shape, color, x, y, z, rx, ry, rz, s);
-    }
-
-    function addLineShape(shape, color, x, y, z, rx, ry, rz, s) {
-        // lines
-
-        shape.autoClose = true;
-
-        const points = shape.getPoints();
-        const spacedPoints = shape.getSpacedPoints(50);
-
-        const geometryPoints = new THREE.BufferGeometry().setFromPoints(points);
-        const geometrySpacedPoints = new THREE.BufferGeometry().setFromPoints(spacedPoints);
-
-        // solid line
-
-        let line = new THREE.Line(geometryPoints, new THREE.LineBasicMaterial({ color: color }));
-        line.position.set(x, y, z - 25);
-        line.rotation.set(rx, ry, rz);
-        line.scale.set(s, s, s);
-        group.add(line);
-
-        // line from equidistance sampled points
-
-        line = new THREE.Line(geometrySpacedPoints, new THREE.LineBasicMaterial({ color: color }));
-        line.position.set(x, y, z + 25);
-        line.rotation.set(rx, ry, rz);
-        line.scale.set(s, s, s);
-        group.add(line);
-
-        // vertices from real points
-
-        let particles = new THREE.Points(geometryPoints, new THREE.PointsMaterial({ color: color, size: 4 }));
-        particles.position.set(x, y, z + 75);
-        particles.rotation.set(rx, ry, rz);
-        particles.scale.set(s, s, s);
-        group.add(particles);
-
-        // equidistance sampled points
-
-        particles = new THREE.Points(geometrySpacedPoints, new THREE.PointsMaterial({ color: color, size: 4 }));
-        particles.position.set(x, y, z + 125);
-        particles.rotation.set(rx, ry, rz);
-        particles.scale.set(s, s, s);
-        group.add(particles);
-    }
-
-    // California
-
-    const californiaPts = [];
-
-    californiaPts.push(new THREE.Vector2(610, 320));
-    californiaPts.push(new THREE.Vector2(450, 300));
-    californiaPts.push(new THREE.Vector2(392, 392));
-    californiaPts.push(new THREE.Vector2(266, 438));
-    californiaPts.push(new THREE.Vector2(190, 570));
-    californiaPts.push(new THREE.Vector2(190, 600));
-    californiaPts.push(new THREE.Vector2(160, 620));
-    californiaPts.push(new THREE.Vector2(160, 650));
-    californiaPts.push(new THREE.Vector2(180, 640));
-    californiaPts.push(new THREE.Vector2(165, 680));
-    californiaPts.push(new THREE.Vector2(150, 670));
-    californiaPts.push(new THREE.Vector2(90, 737));
-    californiaPts.push(new THREE.Vector2(80, 795));
-    californiaPts.push(new THREE.Vector2(50, 835));
-    californiaPts.push(new THREE.Vector2(64, 870));
-    californiaPts.push(new THREE.Vector2(60, 945));
-    californiaPts.push(new THREE.Vector2(300, 945));
-    californiaPts.push(new THREE.Vector2(300, 743));
-    californiaPts.push(new THREE.Vector2(600, 473));
-    californiaPts.push(new THREE.Vector2(626, 425));
-    californiaPts.push(new THREE.Vector2(600, 370));
-    californiaPts.push(new THREE.Vector2(610, 320));
-
-    for (let i = 0; i < californiaPts.length; i++) californiaPts[i].multiplyScalar(0.25);
-
-    const californiaShape = new THREE.Shape(californiaPts);
-
-    // Triangle
-
-    const triangleShape = new THREE.Shape().moveTo(80, 20).lineTo(40, 80).lineTo(120, 80).lineTo(80, 20); // close path
-
-    // Heart
-
-    const x = 0,
-        y = 0;
-
-    const heartShape = new THREE.Shape()
-        .moveTo(x + 25, y + 25)
-        .bezierCurveTo(x + 25, y + 25, x + 20, y, x, y)
-        .bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35)
-        .bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95)
-        .bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35)
-        .bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y)
-        .bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);
-
-    // Square
-
-    const sqLength = 80;
-
-    const squareShape = new THREE.Shape()
-        .moveTo(0, 0)
-        .lineTo(0, sqLength)
-        .lineTo(sqLength, sqLength)
-        .lineTo(sqLength, 0)
-        .lineTo(0, 0);
-
-    // Rounded rectangle
-
-    const roundedRectShape = new THREE.Shape();
-
-    (function roundedRect(ctx, x, y, width, height, radius) {
-        ctx.moveTo(x, y + radius);
-        ctx.lineTo(x, y + height - radius);
-        ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
-        ctx.lineTo(x + width - radius, y + height);
-        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
-        ctx.lineTo(x + width, y + radius);
-        ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
-        ctx.lineTo(x + radius, y);
-        ctx.quadraticCurveTo(x, y, x, y + radius);
-    })(roundedRectShape, 0, 0, 50, 50, 20);
-
-    // Track
-
-    const trackShape = new THREE.Shape()
-        .moveTo(40, 40)
-        .lineTo(40, 160)
-        .absarc(60, 160, 20, Math.PI, 0, true)
-        .lineTo(80, 40)
-        .absarc(60, 40, 20, 2 * Math.PI, Math.PI, true);
-
-    // Circle
-
-    const circleRadius = 40;
-    const circleShape = new THREE.Shape()
-        .moveTo(0, circleRadius)
-        .quadraticCurveTo(circleRadius, circleRadius, circleRadius, 0)
-        .quadraticCurveTo(circleRadius, -circleRadius, 0, -circleRadius)
-        .quadraticCurveTo(-circleRadius, -circleRadius, -circleRadius, 0)
-        .quadraticCurveTo(-circleRadius, circleRadius, 0, circleRadius);
-
-    // Fish
-
-    const fishShape = new THREE.Shape()
-        .moveTo(x, y)
-        .quadraticCurveTo(x + 50, y - 80, x + 90, y - 10)
-        .quadraticCurveTo(x + 100, y - 10, x + 115, y - 40)
-        .quadraticCurveTo(x + 115, y, x + 115, y + 40)
-        .quadraticCurveTo(x + 100, y + 10, x + 90, y + 10)
-        .quadraticCurveTo(x + 50, y + 80, x, y);
-
-    // Arc circle
-
-    const arcShape = new THREE.Shape().moveTo(50, 10).absarc(10, 10, 40, 0, Math.PI * 2, false);
-
-    const holePath = new THREE.Path().moveTo(20, 10).absarc(10, 10, 10, 0, Math.PI * 2, true);
-
-    arcShape.holes.push(holePath);
-
-    // Smiley
-
-    const smileyShape = new THREE.Shape().moveTo(80, 40).absarc(40, 40, 40, 0, Math.PI * 2, false);
-
-    const smileyEye1Path = new THREE.Path().moveTo(35, 20).absellipse(25, 20, 10, 10, 0, Math.PI * 2, true);
-
-    const smileyEye2Path = new THREE.Path().moveTo(65, 20).absarc(55, 20, 10, 0, Math.PI * 2, true);
-
-    const smileyMouthPath = new THREE.Path()
-        .moveTo(20, 40)
-        .quadraticCurveTo(40, 60, 60, 40)
-        .bezierCurveTo(70, 45, 70, 50, 60, 60)
-        .quadraticCurveTo(40, 80, 20, 60)
-        .quadraticCurveTo(5, 50, 20, 40);
-
-    smileyShape.holes.push(smileyEye1Path);
-    smileyShape.holes.push(smileyEye2Path);
-    smileyShape.holes.push(smileyMouthPath);
-
-    // Spline shape
-
-    const splinepts = [];
-    splinepts.push(new THREE.Vector2(70, 20));
-    splinepts.push(new THREE.Vector2(80, 90));
-    splinepts.push(new THREE.Vector2(-30, 70));
-    splinepts.push(new THREE.Vector2(0, 0));
-
-    const splineShape = new THREE.Shape().moveTo(0, 0).splineThru(splinepts);
-
-    const extrudeSettings = {
-        depth: 8,
-        bevelEnabled: true,
-        bevelSegments: 2,
-        steps: 2,
-        bevelSize: 1,
-        bevelThickness: 1,
-    };
-
-    // addShape( shape, color, x, y, z, rx, ry,rz, s );
-
-    addShape(californiaShape, extrudeSettings, 0xf08000, -300, -100, 0, 0, 0, 0, 1);
-    addShape(triangleShape, extrudeSettings, 0x8080f0, -180, 0, 0, 0, 0, 0, 1);
-    addShape(roundedRectShape, extrudeSettings, 0x008000, -150, 150, 0, 0, 0, 0, 1);
-    addShape(trackShape, extrudeSettings, 0x008080, 200, -100, 0, 0, 0, 0, 1);
-    addShape(squareShape, extrudeSettings, 0x0040f0, 150, 100, 0, 0, 0, 0, 1);
-    addShape(heartShape, extrudeSettings, 0xf00000, 60, 100, 0, 0, 0, Math.PI, 1);
-    addShape(circleShape, extrudeSettings, 0x00f000, 120, 250, 0, 0, 0, 0, 1);
-    addShape(fishShape, extrudeSettings, 0x404040, -60, 200, 0, 0, 0, 0, 1);
-    addShape(smileyShape, extrudeSettings, 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
-    addShape(arcShape, extrudeSettings, 0x804000, 150, 0, 0, 0, 0, 0, 1);
-    addShape(splineShape, extrudeSettings, 0x808080, -50, -100, 0, 0, 0, 0, 1);
-
-    addLineShape(arcShape.holes[0], 0x804000, 150, 0, 0, 0, 0, 0, 1);
-
-    for (let i = 0; i < smileyShape.holes.length; i += 1) {
-        addLineShape(smileyShape.holes[i], 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointerdown', onPointerDown);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onPointerDown(event) {
-    if (event.isPrimary === false) return;
-
-    pointerXOnPointerDown = event.clientX - windowHalfX;
-    targetRotationOnPointerDown = targetRotation;
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    pointerX = event.clientX - windowHalfX;
-
-    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
-}
-
-function onPointerUp() {
-    if (event.isPrimary === false) return;
-
-    document.removeEventListener('pointermove', onPointerMove);
-    document.removeEventListener('pointerup', onPointerUp);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_teapot.ts b/examples-testing/examples/webgl_geometry_teapot.ts
deleted file mode 100644
index 4c884a559..000000000
--- a/examples-testing/examples/webgl_geometry_teapot.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
-
-let camera, scene, renderer;
-let cameraControls;
-let effectController;
-const teapotSize = 300;
-let ambientLight, light;
-
-let tess = -1; // force initialization
-let bBottom;
-let bLid;
-let bBody;
-let bFitLid;
-let bNonBlinn;
-let shading;
-
-let teapot, textureCube;
-const materials = {};
-
-init();
-render();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    const canvasWidth = window.innerWidth;
-    const canvasHeight = window.innerHeight;
-
-    // CAMERA
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 80000);
-    camera.position.set(-600, 550, 1300);
-
-    // LIGHTS
-    ambientLight = new THREE.AmbientLight(0x7c7c7c, 3.0);
-
-    light = new THREE.DirectionalLight(0xffffff, 3.0);
-    light.position.set(0.32, 0.39, 0.7);
-
-    // RENDERER
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(canvasWidth, canvasHeight);
-    container.appendChild(renderer.domElement);
-
-    // EVENTS
-    window.addEventListener('resize', onWindowResize);
-
-    // CONTROLS
-    cameraControls = new OrbitControls(camera, renderer.domElement);
-    cameraControls.addEventListener('change', render);
-
-    // TEXTURE MAP
-    const textureMap = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    textureMap.wrapS = textureMap.wrapT = THREE.RepeatWrapping;
-    textureMap.anisotropy = 16;
-    textureMap.colorSpace = THREE.SRGBColorSpace;
-
-    // REFLECTION MAP
-    const path = 'textures/cube/pisa/';
-    const urls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
-
-    textureCube = new THREE.CubeTextureLoader().setPath(path).load(urls);
-
-    materials['wireframe'] = new THREE.MeshBasicMaterial({ wireframe: true });
-    materials['flat'] = new THREE.MeshPhongMaterial({ specular: 0x000000, flatShading: true, side: THREE.DoubleSide });
-    materials['smooth'] = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide });
-    materials['glossy'] = new THREE.MeshPhongMaterial({ side: THREE.DoubleSide });
-    materials['textured'] = new THREE.MeshPhongMaterial({ map: textureMap, side: THREE.DoubleSide });
-    materials['reflective'] = new THREE.MeshPhongMaterial({ envMap: textureCube, side: THREE.DoubleSide });
-
-    // scene itself
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xaaaaaa);
-
-    scene.add(ambientLight);
-    scene.add(light);
-
-    // GUI
-    setupGui();
-}
-
-// EVENT HANDLERS
-
-function onWindowResize() {
-    const canvasWidth = window.innerWidth;
-    const canvasHeight = window.innerHeight;
-
-    renderer.setSize(canvasWidth, canvasHeight);
-
-    camera.aspect = canvasWidth / canvasHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function setupGui() {
-    effectController = {
-        newTess: 15,
-        bottom: true,
-        lid: true,
-        body: true,
-        fitLid: false,
-        nonblinn: false,
-        newShading: 'glossy',
-    };
-
-    const gui = new GUI();
-    gui.add(effectController, 'newTess', [2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50])
-        .name('Tessellation Level')
-        .onChange(render);
-    gui.add(effectController, 'lid').name('display lid').onChange(render);
-    gui.add(effectController, 'body').name('display body').onChange(render);
-    gui.add(effectController, 'bottom').name('display bottom').onChange(render);
-    gui.add(effectController, 'fitLid').name('snug lid').onChange(render);
-    gui.add(effectController, 'nonblinn').name('original scale').onChange(render);
-    gui.add(effectController, 'newShading', ['wireframe', 'flat', 'smooth', 'glossy', 'textured', 'reflective'])
-        .name('Shading')
-        .onChange(render);
-}
-
-//
-
-function render() {
-    if (
-        effectController.newTess !== tess ||
-        effectController.bottom !== bBottom ||
-        effectController.lid !== bLid ||
-        effectController.body !== bBody ||
-        effectController.fitLid !== bFitLid ||
-        effectController.nonblinn !== bNonBlinn ||
-        effectController.newShading !== shading
-    ) {
-        tess = effectController.newTess;
-        bBottom = effectController.bottom;
-        bLid = effectController.lid;
-        bBody = effectController.body;
-        bFitLid = effectController.fitLid;
-        bNonBlinn = effectController.nonblinn;
-        shading = effectController.newShading;
-
-        createNewTeapot();
-    }
-
-    // skybox is rendered separately, so that it is always behind the teapot.
-    if (shading === 'reflective') {
-        scene.background = textureCube;
-    } else {
-        scene.background = null;
-    }
-
-    renderer.render(scene, camera);
-}
-
-// Whenever the teapot changes, the scene is rebuilt from scratch (not much to it).
-function createNewTeapot() {
-    if (teapot !== undefined) {
-        teapot.geometry.dispose();
-        scene.remove(teapot);
-    }
-
-    const geometry = new TeapotGeometry(
-        teapotSize,
-        tess,
-        effectController.bottom,
-        effectController.lid,
-        effectController.body,
-        effectController.fitLid,
-        !effectController.nonblinn,
-    );
-
-    teapot = new THREE.Mesh(geometry, materials[shading]);
-
-    scene.add(teapot);
-}
diff --git a/examples-testing/examples/webgl_geometry_terrain.ts b/examples-testing/examples/webgl_geometry_terrain.ts
deleted file mode 100644
index 8b6ed84ea..000000000
--- a/examples-testing/examples/webgl_geometry_terrain.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-
-let container, stats;
-let camera, controls, scene, renderer;
-let mesh, texture;
-
-const worldWidth = 256,
-    worldDepth = 256;
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xefd1b5);
-    scene.fog = new THREE.FogExp2(0xefd1b5, 0.0025);
-
-    const data = generateHeight(worldWidth, worldDepth);
-
-    camera.position.set(100, 800, -800);
-    camera.lookAt(-100, 810, -800);
-
-    const geometry = new THREE.PlaneGeometry(7500, 7500, worldWidth - 1, worldDepth - 1);
-    geometry.rotateX(-Math.PI / 2);
-
-    const vertices = geometry.attributes.position.array;
-
-    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
-        vertices[j + 1] = data[i] * 10;
-    }
-
-    texture = new THREE.CanvasTexture(generateTexture(data, worldWidth, worldDepth));
-    texture.wrapS = THREE.ClampToEdgeWrapping;
-    texture.wrapT = THREE.ClampToEdgeWrapping;
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map: texture }));
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-    controls.movementSpeed = 150;
-    controls.lookSpeed = 0.1;
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-function generateHeight(width, height) {
-    let seed = Math.PI / 4;
-    window.Math.random = function () {
-        const x = Math.sin(seed++) * 10000;
-        return x - Math.floor(x);
-    };
-
-    const size = width * height,
-        data = new Uint8Array(size);
-    const perlin = new ImprovedNoise(),
-        z = Math.random() * 100;
-
-    let quality = 1;
-
-    for (let j = 0; j < 4; j++) {
-        for (let i = 0; i < size; i++) {
-            const x = i % width,
-                y = ~~(i / width);
-            data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality * 1.75);
-        }
-
-        quality *= 5;
-    }
-
-    return data;
-}
-
-function generateTexture(data, width, height) {
-    let context, image, imageData, shade;
-
-    const vector3 = new THREE.Vector3(0, 0, 0);
-
-    const sun = new THREE.Vector3(1, 1, 1);
-    sun.normalize();
-
-    const canvas = document.createElement('canvas');
-    canvas.width = width;
-    canvas.height = height;
-
-    context = canvas.getContext('2d');
-    context.fillStyle = '#000';
-    context.fillRect(0, 0, width, height);
-
-    image = context.getImageData(0, 0, canvas.width, canvas.height);
-    imageData = image.data;
-
-    for (let i = 0, j = 0, l = imageData.length; i < l; i += 4, j++) {
-        vector3.x = data[j - 2] - data[j + 2];
-        vector3.y = 2;
-        vector3.z = data[j - width * 2] - data[j + width * 2];
-        vector3.normalize();
-
-        shade = vector3.dot(sun);
-
-        imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
-        imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
-        imageData[i + 2] = shade * 96 * (0.5 + data[j] * 0.007);
-    }
-
-    context.putImageData(image, 0, 0);
-
-    // Scaled 4x
-
-    const canvasScaled = document.createElement('canvas');
-    canvasScaled.width = width * 4;
-    canvasScaled.height = height * 4;
-
-    context = canvasScaled.getContext('2d');
-    context.scale(4, 4);
-    context.drawImage(canvas, 0, 0);
-
-    image = context.getImageData(0, 0, canvasScaled.width, canvasScaled.height);
-    imageData = image.data;
-
-    for (let i = 0, l = imageData.length; i < l; i += 4) {
-        const v = ~~(Math.random() * 5);
-
-        imageData[i] += v;
-        imageData[i + 1] += v;
-        imageData[i + 2] += v;
-    }
-
-    context.putImageData(image, 0, 0);
-
-    return canvasScaled;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    controls.update(clock.getDelta());
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_terrain_raycast.ts b/examples-testing/examples/webgl_geometry_terrain_raycast.ts
deleted file mode 100644
index f1383c138..000000000
--- a/examples-testing/examples/webgl_geometry_terrain_raycast.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-
-let container, stats;
-
-let camera, controls, scene, renderer;
-
-let mesh, texture;
-
-const worldWidth = 256,
-    worldDepth = 256,
-    worldHalfWidth = worldWidth / 2,
-    worldHalfDepth = worldDepth / 2;
-
-let helper;
-
-const raycaster = new THREE.Raycaster();
-const pointer = new THREE.Vector2();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-    container.innerHTML = '';
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xbfd1e5);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 10, 20000);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1000;
-    controls.maxDistance = 10000;
-    controls.maxPolarAngle = Math.PI / 2;
-
-    //
-
-    const data = generateHeight(worldWidth, worldDepth);
-
-    controls.target.y = data[worldHalfWidth + worldHalfDepth * worldWidth] + 500;
-    camera.position.y = controls.target.y + 2000;
-    camera.position.x = 2000;
-    controls.update();
-
-    const geometry = new THREE.PlaneGeometry(7500, 7500, worldWidth - 1, worldDepth - 1);
-    geometry.rotateX(-Math.PI / 2);
-
-    const vertices = geometry.attributes.position.array;
-
-    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
-        vertices[j + 1] = data[i] * 10;
-    }
-
-    //
-
-    texture = new THREE.CanvasTexture(generateTexture(data, worldWidth, worldDepth));
-    texture.wrapS = THREE.ClampToEdgeWrapping;
-    texture.wrapT = THREE.ClampToEdgeWrapping;
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map: texture }));
-    scene.add(mesh);
-
-    const geometryHelper = new THREE.ConeGeometry(20, 100, 3);
-    geometryHelper.translate(0, 50, 0);
-    geometryHelper.rotateX(Math.PI / 2);
-    helper = new THREE.Mesh(geometryHelper, new THREE.MeshNormalMaterial());
-    scene.add(helper);
-
-    container.addEventListener('pointermove', onPointerMove);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function generateHeight(width, height) {
-    const size = width * height,
-        data = new Uint8Array(size),
-        perlin = new ImprovedNoise(),
-        z = Math.random() * 100;
-
-    let quality = 1;
-
-    for (let j = 0; j < 4; j++) {
-        for (let i = 0; i < size; i++) {
-            const x = i % width,
-                y = ~~(i / width);
-            data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality * 1.75);
-        }
-
-        quality *= 5;
-    }
-
-    return data;
-}
-
-function generateTexture(data, width, height) {
-    // bake lighting into texture
-
-    let context, image, imageData, shade;
-
-    const vector3 = new THREE.Vector3(0, 0, 0);
-
-    const sun = new THREE.Vector3(1, 1, 1);
-    sun.normalize();
-
-    const canvas = document.createElement('canvas');
-    canvas.width = width;
-    canvas.height = height;
-
-    context = canvas.getContext('2d');
-    context.fillStyle = '#000';
-    context.fillRect(0, 0, width, height);
-
-    image = context.getImageData(0, 0, canvas.width, canvas.height);
-    imageData = image.data;
-
-    for (let i = 0, j = 0, l = imageData.length; i < l; i += 4, j++) {
-        vector3.x = data[j - 2] - data[j + 2];
-        vector3.y = 2;
-        vector3.z = data[j - width * 2] - data[j + width * 2];
-        vector3.normalize();
-
-        shade = vector3.dot(sun);
-
-        imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
-        imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
-        imageData[i + 2] = shade * 96 * (0.5 + data[j] * 0.007);
-    }
-
-    context.putImageData(image, 0, 0);
-
-    // Scaled 4x
-
-    const canvasScaled = document.createElement('canvas');
-    canvasScaled.width = width * 4;
-    canvasScaled.height = height * 4;
-
-    context = canvasScaled.getContext('2d');
-    context.scale(4, 4);
-    context.drawImage(canvas, 0, 0);
-
-    image = context.getImageData(0, 0, canvasScaled.width, canvasScaled.height);
-    imageData = image.data;
-
-    for (let i = 0, l = imageData.length; i < l; i += 4) {
-        const v = ~~(Math.random() * 5);
-
-        imageData[i] += v;
-        imageData[i + 1] += v;
-        imageData[i + 2] += v;
-    }
-
-    context.putImageData(image, 0, 0);
-
-    return canvasScaled;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
-    pointer.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1;
-    raycaster.setFromCamera(pointer, camera);
-
-    // See if the ray from the camera into the world hits one of our meshes
-    const intersects = raycaster.intersectObject(mesh);
-
-    // Toggle rotation bool for meshes that we clicked
-    if (intersects.length > 0) {
-        helper.position.set(0, 0, 0);
-        helper.lookAt(intersects[0].face.normal);
-
-        helper.position.copy(intersects[0].point);
-    }
-}
diff --git a/examples-testing/examples/webgl_geometry_text.ts b/examples-testing/examples/webgl_geometry_text.ts
deleted file mode 100644
index 831ebcd6b..000000000
--- a/examples-testing/examples/webgl_geometry_text.ts
+++ /dev/null
@@ -1,312 +0,0 @@
-import * as THREE from 'three';
-
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-THREE.Cache.enabled = true;
-
-let container;
-
-let camera, cameraTarget, scene, renderer;
-
-let group, textMesh1, textMesh2, textGeo, materials;
-
-let firstLetter = true;
-
-let text = 'three.js',
-    bevelEnabled = true,
-    font = undefined,
-    fontName = 'optimer', // helvetiker, optimer, gentilis, droid sans, droid serif
-    fontWeight = 'bold'; // normal bold
-
-const depth = 20,
-    size = 70,
-    hover = 30,
-    curveSegments = 4,
-    bevelThickness = 2,
-    bevelSize = 1.5;
-
-const mirror = true;
-
-const fontMap = {
-    helvetiker: 0,
-    optimer: 1,
-    gentilis: 2,
-    'droid/droid_sans': 3,
-    'droid/droid_serif': 4,
-};
-
-const weightMap = {
-    regular: 0,
-    bold: 1,
-};
-
-const reverseFontMap = [];
-const reverseWeightMap = [];
-
-for (const i in fontMap) reverseFontMap[fontMap[i]] = i;
-for (const i in weightMap) reverseWeightMap[weightMap[i]] = i;
-
-let targetRotation = 0;
-let targetRotationOnPointerDown = 0;
-
-let pointerX = 0;
-let pointerXOnPointerDown = 0;
-
-let windowHalfX = window.innerWidth / 2;
-
-let fontIndex = 1;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
-    camera.position.set(0, 400, 700);
-
-    cameraTarget = new THREE.Vector3(0, 150, 0);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x000000);
-    scene.fog = new THREE.Fog(0x000000, 250, 1400);
-
-    // LIGHTS
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 0.4);
-    dirLight.position.set(0, 0, 1).normalize();
-    scene.add(dirLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 4.5, 0, 0);
-    pointLight.color.setHSL(Math.random(), 1, 0.5);
-    pointLight.position.set(0, 100, 90);
-    scene.add(pointLight);
-
-    materials = [
-        new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true }), // front
-        new THREE.MeshPhongMaterial({ color: 0xffffff }), // side
-    ];
-
-    group = new THREE.Group();
-    group.position.y = 100;
-
-    scene.add(group);
-
-    loadFont();
-
-    const plane = new THREE.Mesh(
-        new THREE.PlaneGeometry(10000, 10000),
-        new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }),
-    );
-    plane.position.y = 100;
-    plane.rotation.x = -Math.PI / 2;
-    scene.add(plane);
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // EVENTS
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointerdown', onPointerDown);
-
-    document.addEventListener('keypress', onDocumentKeyPress);
-    document.addEventListener('keydown', onDocumentKeyDown);
-
-    //
-
-    const params = {
-        changeColor: function () {
-            pointLight.color.setHSL(Math.random(), 1, 0.5);
-        },
-        changeFont: function () {
-            fontIndex++;
-
-            fontName = reverseFontMap[fontIndex % reverseFontMap.length];
-
-            loadFont();
-        },
-        changeWeight: function () {
-            if (fontWeight === 'bold') {
-                fontWeight = 'regular';
-            } else {
-                fontWeight = 'bold';
-            }
-
-            loadFont();
-        },
-        changeBevel: function () {
-            bevelEnabled = !bevelEnabled;
-
-            refreshText();
-        },
-    };
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'changeColor').name('change color');
-    gui.add(params, 'changeFont').name('change font');
-    gui.add(params, 'changeWeight').name('change weight');
-    gui.add(params, 'changeBevel').name('change bevel');
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onDocumentKeyDown(event) {
-    if (firstLetter) {
-        firstLetter = false;
-        text = '';
-    }
-
-    const keyCode = event.keyCode;
-
-    // backspace
-
-    if (keyCode == 8) {
-        event.preventDefault();
-
-        text = text.substring(0, text.length - 1);
-        refreshText();
-
-        return false;
-    }
-}
-
-function onDocumentKeyPress(event) {
-    const keyCode = event.which;
-
-    // backspace
-
-    if (keyCode == 8) {
-        event.preventDefault();
-    } else {
-        const ch = String.fromCharCode(keyCode);
-        text += ch;
-
-        refreshText();
-    }
-}
-
-function loadFont() {
-    const loader = new FontLoader();
-    loader.load('fonts/' + fontName + '_' + fontWeight + '.typeface.json', function (response) {
-        font = response;
-
-        refreshText();
-    });
-}
-
-function createText() {
-    textGeo = new TextGeometry(text, {
-        font: font,
-
-        size: size,
-        depth: depth,
-        curveSegments: curveSegments,
-
-        bevelThickness: bevelThickness,
-        bevelSize: bevelSize,
-        bevelEnabled: bevelEnabled,
-    });
-
-    textGeo.computeBoundingBox();
-
-    const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
-
-    textMesh1 = new THREE.Mesh(textGeo, materials);
-
-    textMesh1.position.x = centerOffset;
-    textMesh1.position.y = hover;
-    textMesh1.position.z = 0;
-
-    textMesh1.rotation.x = 0;
-    textMesh1.rotation.y = Math.PI * 2;
-
-    group.add(textMesh1);
-
-    if (mirror) {
-        textMesh2 = new THREE.Mesh(textGeo, materials);
-
-        textMesh2.position.x = centerOffset;
-        textMesh2.position.y = -hover;
-        textMesh2.position.z = depth;
-
-        textMesh2.rotation.x = Math.PI;
-        textMesh2.rotation.y = Math.PI * 2;
-
-        group.add(textMesh2);
-    }
-}
-
-function refreshText() {
-    group.remove(textMesh1);
-    if (mirror) group.remove(textMesh2);
-
-    if (!text) return;
-
-    createText();
-}
-
-function onPointerDown(event) {
-    if (event.isPrimary === false) return;
-
-    pointerXOnPointerDown = event.clientX - windowHalfX;
-    targetRotationOnPointerDown = targetRotation;
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    pointerX = event.clientX - windowHalfX;
-
-    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
-}
-
-function onPointerUp() {
-    if (event.isPrimary === false) return;
-
-    document.removeEventListener('pointermove', onPointerMove);
-    document.removeEventListener('pointerup', onPointerUp);
-}
-
-//
-
-function animate() {
-    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
-
-    camera.lookAt(cameraTarget);
-
-    renderer.clear();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_text_shapes.ts b/examples-testing/examples/webgl_geometry_text_shapes.ts
deleted file mode 100644
index adfb6008d..000000000
--- a/examples-testing/examples/webgl_geometry_text_shapes.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(0, -400, 600);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const loader = new FontLoader();
-    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
-        const color = 0x006699;
-
-        const matDark = new THREE.LineBasicMaterial({
-            color: color,
-            side: THREE.DoubleSide,
-        });
-
-        const matLite = new THREE.MeshBasicMaterial({
-            color: color,
-            transparent: true,
-            opacity: 0.4,
-            side: THREE.DoubleSide,
-        });
-
-        const message = '   Three.js\nSimple text.';
-
-        const shapes = font.generateShapes(message, 100);
-
-        const geometry = new THREE.ShapeGeometry(shapes);
-
-        geometry.computeBoundingBox();
-
-        const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
-
-        geometry.translate(xMid, 0, 0);
-
-        // make shape ( N.B. edge view not visible )
-
-        const text = new THREE.Mesh(geometry, matLite);
-        text.position.z = -150;
-        scene.add(text);
-
-        // make line shape ( N.B. edge view remains visible )
-
-        const holeShapes = [];
-
-        for (let i = 0; i < shapes.length; i++) {
-            const shape = shapes[i];
-
-            if (shape.holes && shape.holes.length > 0) {
-                for (let j = 0; j < shape.holes.length; j++) {
-                    const hole = shape.holes[j];
-                    holeShapes.push(hole);
-                }
-            }
-        }
-
-        shapes.push.apply(shapes, holeShapes);
-
-        const lineText = new THREE.Object3D();
-
-        for (let i = 0; i < shapes.length; i++) {
-            const shape = shapes[i];
-
-            const points = shape.getPoints();
-            const geometry = new THREE.BufferGeometry().setFromPoints(points);
-
-            geometry.translate(xMid, 0, 0);
-
-            const lineMesh = new THREE.Line(geometry, matDark);
-            lineText.add(lineMesh);
-        }
-
-        scene.add(lineText);
-
-        render();
-    }); //end load function
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0, 0);
-    controls.update();
-
-    controls.addEventListener('change', render);
-
-    window.addEventListener('resize', onWindowResize);
-} // end init
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_geometry_text_stroke.ts b/examples-testing/examples/webgl_geometry_text_stroke.ts
deleted file mode 100644
index 9a1983253..000000000
--- a/examples-testing/examples/webgl_geometry_text_stroke.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(0, -400, 600);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const loader = new FontLoader();
-    loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
-        const color = new THREE.Color(0x006699);
-
-        const matDark = new THREE.MeshBasicMaterial({
-            color: color,
-            side: THREE.DoubleSide,
-        });
-
-        const matLite = new THREE.MeshBasicMaterial({
-            color: color,
-            transparent: true,
-            opacity: 0.4,
-            side: THREE.DoubleSide,
-        });
-
-        const message = '   Three.js\nStroke text.';
-
-        const shapes = font.generateShapes(message, 100);
-
-        const geometry = new THREE.ShapeGeometry(shapes);
-
-        geometry.computeBoundingBox();
-
-        const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
-
-        geometry.translate(xMid, 0, 0);
-
-        // make shape ( N.B. edge view not visible )
-
-        const text = new THREE.Mesh(geometry, matLite);
-        text.position.z = -150;
-        scene.add(text);
-
-        // make line shape ( N.B. edge view remains visible )
-
-        const holeShapes = [];
-
-        for (let i = 0; i < shapes.length; i++) {
-            const shape = shapes[i];
-
-            if (shape.holes && shape.holes.length > 0) {
-                for (let j = 0; j < shape.holes.length; j++) {
-                    const hole = shape.holes[j];
-                    holeShapes.push(hole);
-                }
-            }
-        }
-
-        shapes.push.apply(shapes, holeShapes);
-
-        const style = SVGLoader.getStrokeStyle(5, color.getStyle());
-
-        const strokeText = new THREE.Group();
-
-        for (let i = 0; i < shapes.length; i++) {
-            const shape = shapes[i];
-
-            const points = shape.getPoints();
-
-            const geometry = SVGLoader.pointsToStroke(points, style);
-
-            geometry.translate(xMid, 0, 0);
-
-            const strokeMesh = new THREE.Mesh(geometry, matDark);
-            strokeText.add(strokeMesh);
-        }
-
-        scene.add(strokeText);
-
-        render();
-    }); //end load function
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0, 0);
-    controls.update();
-
-    controls.addEventListener('change', render);
-
-    window.addEventListener('resize', onWindowResize);
-} // end init
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_gpgpu_birds.ts b/examples-testing/examples/webgl_gpgpu_birds.ts
deleted file mode 100644
index 20a5e0d97..000000000
--- a/examples-testing/examples/webgl_gpgpu_birds.ts
+++ /dev/null
@@ -1,313 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
-
-/* TEXTURE WIDTH FOR SIMULATION */
-const WIDTH = 32;
-
-const BIRDS = WIDTH * WIDTH;
-
-// Custom Geometry - using 3 triangles each. No UVs, no normals currently.
-class BirdGeometry extends THREE.BufferGeometry {
-    constructor() {
-        super();
-
-        const trianglesPerBird = 3;
-        const triangles = BIRDS * trianglesPerBird;
-        const points = triangles * 3;
-
-        const vertices = new THREE.BufferAttribute(new Float32Array(points * 3), 3);
-        const birdColors = new THREE.BufferAttribute(new Float32Array(points * 3), 3);
-        const references = new THREE.BufferAttribute(new Float32Array(points * 2), 2);
-        const birdVertex = new THREE.BufferAttribute(new Float32Array(points), 1);
-
-        this.setAttribute('position', vertices);
-        this.setAttribute('birdColor', birdColors);
-        this.setAttribute('reference', references);
-        this.setAttribute('birdVertex', birdVertex);
-
-        // this.setAttribute( 'normal', new Float32Array( points * 3 ), 3 );
-
-        let v = 0;
-
-        function verts_push() {
-            for (let i = 0; i < arguments.length; i++) {
-                vertices.array[v++] = arguments[i];
-            }
-        }
-
-        const wingsSpan = 20;
-
-        for (let f = 0; f < BIRDS; f++) {
-            // Body
-
-            verts_push(0, -0, -20, 0, 4, -20, 0, 0, 30);
-
-            // Wings
-
-            verts_push(0, 0, -15, -wingsSpan, 0, 0, 0, 0, 15);
-
-            verts_push(0, 0, 15, wingsSpan, 0, 0, 0, 0, -15);
-        }
-
-        for (let v = 0; v < triangles * 3; v++) {
-            const triangleIndex = ~~(v / 3);
-            const birdIndex = ~~(triangleIndex / trianglesPerBird);
-            const x = (birdIndex % WIDTH) / WIDTH;
-            const y = ~~(birdIndex / WIDTH) / WIDTH;
-
-            const c = new THREE.Color(0x666666 + (~~(v / 9) / BIRDS) * 0x666666);
-
-            birdColors.array[v * 3 + 0] = c.r;
-            birdColors.array[v * 3 + 1] = c.g;
-            birdColors.array[v * 3 + 2] = c.b;
-
-            references.array[v * 2] = x;
-            references.array[v * 2 + 1] = y;
-
-            birdVertex.array[v] = v % 9;
-        }
-
-        this.scale(0.2, 0.2, 0.2);
-    }
-}
-
-//
-
-let container, stats;
-let camera, scene, renderer;
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-const BOUNDS = 800,
-    BOUNDS_HALF = BOUNDS / 2;
-
-let last = performance.now();
-
-let gpuCompute;
-let velocityVariable;
-let positionVariable;
-let positionUniforms;
-let velocityUniforms;
-let birdUniforms;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
-    camera.position.z = 350;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-    scene.fog = new THREE.Fog(0xffffff, 100, 1000);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    initComputeRenderer();
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    const effectController = {
-        separation: 20.0,
-        alignment: 20.0,
-        cohesion: 20.0,
-        freedom: 0.75,
-    };
-
-    const valuesChanger = function () {
-        velocityUniforms['separationDistance'].value = effectController.separation;
-        velocityUniforms['alignmentDistance'].value = effectController.alignment;
-        velocityUniforms['cohesionDistance'].value = effectController.cohesion;
-        velocityUniforms['freedomFactor'].value = effectController.freedom;
-    };
-
-    valuesChanger();
-
-    gui.add(effectController, 'separation', 0.0, 100.0, 1.0).onChange(valuesChanger);
-    gui.add(effectController, 'alignment', 0.0, 100, 0.001).onChange(valuesChanger);
-    gui.add(effectController, 'cohesion', 0.0, 100, 0.025).onChange(valuesChanger);
-    gui.close();
-
-    initBirds();
-}
-
-function initComputeRenderer() {
-    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
-
-    const dtPosition = gpuCompute.createTexture();
-    const dtVelocity = gpuCompute.createTexture();
-    fillPositionTexture(dtPosition);
-    fillVelocityTexture(dtVelocity);
-
-    velocityVariable = gpuCompute.addVariable(
-        'textureVelocity',
-        document.getElementById('fragmentShaderVelocity').textContent,
-        dtVelocity,
-    );
-    positionVariable = gpuCompute.addVariable(
-        'texturePosition',
-        document.getElementById('fragmentShaderPosition').textContent,
-        dtPosition,
-    );
-
-    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
-    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
-
-    positionUniforms = positionVariable.material.uniforms;
-    velocityUniforms = velocityVariable.material.uniforms;
-
-    positionUniforms['time'] = { value: 0.0 };
-    positionUniforms['delta'] = { value: 0.0 };
-    velocityUniforms['time'] = { value: 1.0 };
-    velocityUniforms['delta'] = { value: 0.0 };
-    velocityUniforms['testing'] = { value: 1.0 };
-    velocityUniforms['separationDistance'] = { value: 1.0 };
-    velocityUniforms['alignmentDistance'] = { value: 1.0 };
-    velocityUniforms['cohesionDistance'] = { value: 1.0 };
-    velocityUniforms['freedomFactor'] = { value: 1.0 };
-    velocityUniforms['predator'] = { value: new THREE.Vector3() };
-    velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed(2);
-
-    velocityVariable.wrapS = THREE.RepeatWrapping;
-    velocityVariable.wrapT = THREE.RepeatWrapping;
-    positionVariable.wrapS = THREE.RepeatWrapping;
-    positionVariable.wrapT = THREE.RepeatWrapping;
-
-    const error = gpuCompute.init();
-
-    if (error !== null) {
-        console.error(error);
-    }
-}
-
-function initBirds() {
-    const geometry = new BirdGeometry();
-
-    // For Vertex and Fragment
-    birdUniforms = {
-        color: { value: new THREE.Color(0xff2200) },
-        texturePosition: { value: null },
-        textureVelocity: { value: null },
-        time: { value: 1.0 },
-        delta: { value: 0.0 },
-    };
-
-    // THREE.ShaderMaterial
-    const material = new THREE.ShaderMaterial({
-        uniforms: birdUniforms,
-        vertexShader: document.getElementById('birdVS').textContent,
-        fragmentShader: document.getElementById('birdFS').textContent,
-        side: THREE.DoubleSide,
-    });
-
-    const birdMesh = new THREE.Mesh(geometry, material);
-    birdMesh.rotation.y = Math.PI / 2;
-    birdMesh.matrixAutoUpdate = false;
-    birdMesh.updateMatrix();
-
-    scene.add(birdMesh);
-}
-
-function fillPositionTexture(texture) {
-    const theArray = texture.image.data;
-
-    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
-        const x = Math.random() * BOUNDS - BOUNDS_HALF;
-        const y = Math.random() * BOUNDS - BOUNDS_HALF;
-        const z = Math.random() * BOUNDS - BOUNDS_HALF;
-
-        theArray[k + 0] = x;
-        theArray[k + 1] = y;
-        theArray[k + 2] = z;
-        theArray[k + 3] = 1;
-    }
-}
-
-function fillVelocityTexture(texture) {
-    const theArray = texture.image.data;
-
-    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
-        const x = Math.random() - 0.5;
-        const y = Math.random() - 0.5;
-        const z = Math.random() - 0.5;
-
-        theArray[k + 0] = x * 10;
-        theArray[k + 1] = y * 10;
-        theArray[k + 2] = z * 10;
-        theArray[k + 3] = 1;
-    }
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const now = performance.now();
-    let delta = (now - last) / 1000;
-
-    if (delta > 1) delta = 1; // safety cap on large deltas
-    last = now;
-
-    positionUniforms['time'].value = now;
-    positionUniforms['delta'].value = delta;
-    velocityUniforms['time'].value = now;
-    velocityUniforms['delta'].value = delta;
-    birdUniforms['time'].value = now;
-    birdUniforms['delta'].value = delta;
-
-    velocityUniforms['predator'].value.set((0.5 * mouseX) / windowHalfX, (-0.5 * mouseY) / windowHalfY, 0);
-
-    mouseX = 10000;
-    mouseY = 10000;
-
-    gpuCompute.compute();
-
-    birdUniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
-    birdUniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_gpgpu_birds_gltf.ts b/examples-testing/examples/webgl_gpgpu_birds_gltf.ts
deleted file mode 100644
index 3176b95a9..000000000
--- a/examples-testing/examples/webgl_gpgpu_birds_gltf.ts
+++ /dev/null
@@ -1,415 +0,0 @@
-import * as THREE from 'three';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
-
-/* TEXTURE WIDTH FOR SIMULATION */
-const WIDTH = 64;
-const BIRDS = WIDTH * WIDTH;
-
-/* BAKE ANIMATION INTO TEXTURE and CREATE GEOMETRY FROM BASE MODEL */
-const BirdGeometry = new THREE.BufferGeometry();
-let textureAnimation, durationAnimation, birdMesh, materialShader, indicesPerBird;
-
-function nextPowerOf2(n) {
-    return Math.pow(2, Math.ceil(Math.log(n) / Math.log(2)));
-}
-
-Math.lerp = function (value1, value2, amount) {
-    amount = Math.max(Math.min(amount, 1), 0);
-    return value1 + (value2 - value1) * amount;
-};
-
-const gltfs = ['models/gltf/Parrot.glb', 'models/gltf/Flamingo.glb'];
-const colors = [0xccffff, 0xffdeff];
-const sizes = [0.2, 0.1];
-const selectModel = Math.floor(Math.random() * gltfs.length);
-new GLTFLoader().load(gltfs[selectModel], function (gltf) {
-    const animations = gltf.animations;
-    durationAnimation = Math.round(animations[0].duration * 60);
-    const birdGeo = gltf.scene.children[0].geometry;
-    const morphAttributes = birdGeo.morphAttributes.position;
-    const tHeight = nextPowerOf2(durationAnimation);
-    const tWidth = nextPowerOf2(birdGeo.getAttribute('position').count);
-    indicesPerBird = birdGeo.index.count;
-    const tData = new Float32Array(4 * tWidth * tHeight);
-
-    for (let i = 0; i < tWidth; i++) {
-        for (let j = 0; j < tHeight; j++) {
-            const offset = j * tWidth * 4;
-
-            const curMorph = Math.floor((j / durationAnimation) * morphAttributes.length);
-            const nextMorph =
-                (Math.floor((j / durationAnimation) * morphAttributes.length) + 1) % morphAttributes.length;
-            const lerpAmount = ((j / durationAnimation) * morphAttributes.length) % 1;
-
-            if (j < durationAnimation) {
-                let d0, d1;
-
-                d0 = morphAttributes[curMorph].array[i * 3];
-                d1 = morphAttributes[nextMorph].array[i * 3];
-
-                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4] = Math.lerp(d0, d1, lerpAmount);
-
-                d0 = morphAttributes[curMorph].array[i * 3 + 1];
-                d1 = morphAttributes[nextMorph].array[i * 3 + 1];
-
-                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4 + 1] = Math.lerp(d0, d1, lerpAmount);
-
-                d0 = morphAttributes[curMorph].array[i * 3 + 2];
-                d1 = morphAttributes[nextMorph].array[i * 3 + 2];
-
-                if (d0 !== undefined && d1 !== undefined) tData[offset + i * 4 + 2] = Math.lerp(d0, d1, lerpAmount);
-
-                tData[offset + i * 4 + 3] = 1;
-            }
-        }
-    }
-
-    textureAnimation = new THREE.DataTexture(tData, tWidth, tHeight, THREE.RGBAFormat, THREE.FloatType);
-    textureAnimation.needsUpdate = true;
-
-    const vertices = [],
-        color = [],
-        reference = [],
-        seeds = [],
-        indices = [];
-    const totalVertices = birdGeo.getAttribute('position').count * 3 * BIRDS;
-    for (let i = 0; i < totalVertices; i++) {
-        const bIndex = i % (birdGeo.getAttribute('position').count * 3);
-        vertices.push(birdGeo.getAttribute('position').array[bIndex]);
-        color.push(birdGeo.getAttribute('color').array[bIndex]);
-    }
-
-    let r = Math.random();
-    for (let i = 0; i < birdGeo.getAttribute('position').count * BIRDS; i++) {
-        const bIndex = i % birdGeo.getAttribute('position').count;
-        const bird = Math.floor(i / birdGeo.getAttribute('position').count);
-        if (bIndex == 0) r = Math.random();
-        const j = ~~bird;
-        const x = (j % WIDTH) / WIDTH;
-        const y = ~~(j / WIDTH) / WIDTH;
-        reference.push(x, y, bIndex / tWidth, durationAnimation / tHeight);
-        seeds.push(bird, r, Math.random(), Math.random());
-    }
-
-    for (let i = 0; i < birdGeo.index.array.length * BIRDS; i++) {
-        const offset = Math.floor(i / birdGeo.index.array.length) * birdGeo.getAttribute('position').count;
-        indices.push(birdGeo.index.array[i % birdGeo.index.array.length] + offset);
-    }
-
-    BirdGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
-    BirdGeometry.setAttribute('birdColor', new THREE.BufferAttribute(new Float32Array(color), 3));
-    BirdGeometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(color), 3));
-    BirdGeometry.setAttribute('reference', new THREE.BufferAttribute(new Float32Array(reference), 4));
-    BirdGeometry.setAttribute('seeds', new THREE.BufferAttribute(new Float32Array(seeds), 4));
-
-    BirdGeometry.setIndex(indices);
-
-    init();
-});
-
-let container, stats;
-let camera, scene, renderer;
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-const BOUNDS = 800,
-    BOUNDS_HALF = BOUNDS / 2;
-
-let last = performance.now();
-
-let gpuCompute;
-let velocityVariable;
-let positionVariable;
-let positionUniforms;
-let velocityUniforms;
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
-    camera.position.z = 350;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(colors[selectModel]);
-    scene.fog = new THREE.Fog(colors[selectModel], 100, 1000);
-
-    // LIGHTS
-
-    const hemiLight = new THREE.HemisphereLight(colors[selectModel], 0xffffff, 4.5);
-    hemiLight.color.setHSL(0.6, 1, 0.6, THREE.SRGBColorSpace);
-    hemiLight.groundColor.setHSL(0.095, 1, 0.75, THREE.SRGBColorSpace);
-    hemiLight.position.set(0, 50, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0x00ced1, 2.0);
-    dirLight.color.setHSL(0.1, 1, 0.95, THREE.SRGBColorSpace);
-    dirLight.position.set(-1, 1.75, 1);
-    dirLight.position.multiplyScalar(30);
-    scene.add(dirLight);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    initComputeRenderer();
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointermove', onPointerMove);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    const effectController = {
-        separation: 20.0,
-        alignment: 20.0,
-        cohesion: 20.0,
-        freedom: 0.75,
-        size: sizes[selectModel],
-        count: Math.floor(BIRDS / 4),
-    };
-
-    const valuesChanger = function () {
-        velocityUniforms['separationDistance'].value = effectController.separation;
-        velocityUniforms['alignmentDistance'].value = effectController.alignment;
-        velocityUniforms['cohesionDistance'].value = effectController.cohesion;
-        velocityUniforms['freedomFactor'].value = effectController.freedom;
-        if (materialShader) materialShader.uniforms['size'].value = effectController.size;
-        BirdGeometry.setDrawRange(0, indicesPerBird * effectController.count);
-    };
-
-    valuesChanger();
-
-    gui.add(effectController, 'separation', 0.0, 100.0, 1.0).onChange(valuesChanger);
-    gui.add(effectController, 'alignment', 0.0, 100, 0.001).onChange(valuesChanger);
-    gui.add(effectController, 'cohesion', 0.0, 100, 0.025).onChange(valuesChanger);
-    gui.add(effectController, 'size', 0, 1, 0.01).onChange(valuesChanger);
-    gui.add(effectController, 'count', 0, BIRDS, 1).onChange(valuesChanger);
-    gui.close();
-
-    initBirds(effectController);
-}
-
-function initComputeRenderer() {
-    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
-
-    const dtPosition = gpuCompute.createTexture();
-    const dtVelocity = gpuCompute.createTexture();
-    fillPositionTexture(dtPosition);
-    fillVelocityTexture(dtVelocity);
-
-    velocityVariable = gpuCompute.addVariable(
-        'textureVelocity',
-        document.getElementById('fragmentShaderVelocity').textContent,
-        dtVelocity,
-    );
-    positionVariable = gpuCompute.addVariable(
-        'texturePosition',
-        document.getElementById('fragmentShaderPosition').textContent,
-        dtPosition,
-    );
-
-    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
-    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
-
-    positionUniforms = positionVariable.material.uniforms;
-    velocityUniforms = velocityVariable.material.uniforms;
-
-    positionUniforms['time'] = { value: 0.0 };
-    positionUniforms['delta'] = { value: 0.0 };
-    velocityUniforms['time'] = { value: 1.0 };
-    velocityUniforms['delta'] = { value: 0.0 };
-    velocityUniforms['testing'] = { value: 1.0 };
-    velocityUniforms['separationDistance'] = { value: 1.0 };
-    velocityUniforms['alignmentDistance'] = { value: 1.0 };
-    velocityUniforms['cohesionDistance'] = { value: 1.0 };
-    velocityUniforms['freedomFactor'] = { value: 1.0 };
-    velocityUniforms['predator'] = { value: new THREE.Vector3() };
-    velocityVariable.material.defines.BOUNDS = BOUNDS.toFixed(2);
-
-    velocityVariable.wrapS = THREE.RepeatWrapping;
-    velocityVariable.wrapT = THREE.RepeatWrapping;
-    positionVariable.wrapS = THREE.RepeatWrapping;
-    positionVariable.wrapT = THREE.RepeatWrapping;
-
-    const error = gpuCompute.init();
-
-    if (error !== null) {
-        console.error(error);
-    }
-}
-
-function initBirds(effectController) {
-    const geometry = BirdGeometry;
-
-    const m = new THREE.MeshStandardMaterial({
-        vertexColors: true,
-        flatShading: true,
-        roughness: 1,
-        metalness: 0,
-    });
-
-    m.onBeforeCompile = shader => {
-        shader.uniforms.texturePosition = { value: null };
-        shader.uniforms.textureVelocity = { value: null };
-        shader.uniforms.textureAnimation = { value: textureAnimation };
-        shader.uniforms.time = { value: 1.0 };
-        shader.uniforms.size = { value: effectController.size };
-        shader.uniforms.delta = { value: 0.0 };
-
-        let token = '#define STANDARD';
-
-        let insert = /* glsl */ `
-						attribute vec4 reference;
-						attribute vec4 seeds;
-						attribute vec3 birdColor;
-						uniform sampler2D texturePosition;
-						uniform sampler2D textureVelocity;
-						uniform sampler2D textureAnimation;
-						uniform float size;
-						uniform float time;
-					`;
-
-        shader.vertexShader = shader.vertexShader.replace(token, token + insert);
-
-        token = '#include <begin_vertex>';
-
-        insert = /* glsl */ `
-						vec4 tmpPos = texture2D( texturePosition, reference.xy );
-
-						vec3 pos = tmpPos.xyz;
-						vec3 velocity = normalize(texture2D( textureVelocity, reference.xy ).xyz);
-						vec3 aniPos = texture2D( textureAnimation, vec2( reference.z, mod( time + ( seeds.x ) * ( ( 0.0004 + seeds.y / 10000.0) + normalize( velocity ) / 20000.0 ), reference.w ) ) ).xyz;
-						vec3 newPosition = position;
-
-						newPosition = mat3( modelMatrix ) * ( newPosition + aniPos );
-						newPosition *= size + seeds.y * size * 0.2;
-
-						velocity.z *= -1.;
-						float xz = length( velocity.xz );
-						float xyz = 1.;
-						float x = sqrt( 1. - velocity.y * velocity.y );
-
-						float cosry = velocity.x / xz;
-						float sinry = velocity.z / xz;
-
-						float cosrz = x / xyz;
-						float sinrz = velocity.y / xyz;
-
-						mat3 maty =  mat3( cosry, 0, -sinry, 0    , 1, 0     , sinry, 0, cosry );
-						mat3 matz =  mat3( cosrz , sinrz, 0, -sinrz, cosrz, 0, 0     , 0    , 1 );
-
-						newPosition =  maty * matz * newPosition;
-						newPosition += pos;
-
-						vec3 transformed = vec3( newPosition );
-					`;
-
-        shader.vertexShader = shader.vertexShader.replace(token, insert);
-
-        materialShader = shader;
-    };
-
-    birdMesh = new THREE.Mesh(geometry, m);
-    birdMesh.rotation.y = Math.PI / 2;
-
-    birdMesh.castShadow = true;
-    birdMesh.receiveShadow = true;
-
-    scene.add(birdMesh);
-}
-
-function fillPositionTexture(texture) {
-    const theArray = texture.image.data;
-
-    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
-        const x = Math.random() * BOUNDS - BOUNDS_HALF;
-        const y = Math.random() * BOUNDS - BOUNDS_HALF;
-        const z = Math.random() * BOUNDS - BOUNDS_HALF;
-
-        theArray[k + 0] = x;
-        theArray[k + 1] = y;
-        theArray[k + 2] = z;
-        theArray[k + 3] = 1;
-    }
-}
-
-function fillVelocityTexture(texture) {
-    const theArray = texture.image.data;
-
-    for (let k = 0, kl = theArray.length; k < kl; k += 4) {
-        const x = Math.random() - 0.5;
-        const y = Math.random() - 0.5;
-        const z = Math.random() - 0.5;
-
-        theArray[k + 0] = x * 10;
-        theArray[k + 1] = y * 10;
-        theArray[k + 2] = z * 10;
-        theArray[k + 3] = 1;
-    }
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const now = performance.now();
-    let delta = (now - last) / 1000;
-
-    if (delta > 1) delta = 1; // safety cap on large deltas
-    last = now;
-
-    positionUniforms['time'].value = now;
-    positionUniforms['delta'].value = delta;
-    velocityUniforms['time'].value = now;
-    velocityUniforms['delta'].value = delta;
-    if (materialShader) materialShader.uniforms['time'].value = now / 1000;
-    if (materialShader) materialShader.uniforms['delta'].value = delta;
-
-    velocityUniforms['predator'].value.set((0.5 * mouseX) / windowHalfX, (-0.5 * mouseY) / windowHalfY, 0);
-
-    mouseX = 10000;
-    mouseY = 10000;
-
-    gpuCompute.compute();
-
-    if (materialShader)
-        materialShader.uniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
-    if (materialShader)
-        materialShader.uniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_gpgpu_protoplanet.ts b/examples-testing/examples/webgl_gpgpu_protoplanet.ts
deleted file mode 100644
index 30444ddba..000000000
--- a/examples-testing/examples/webgl_gpgpu_protoplanet.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
-
-// Texture width for simulation (each texel is a debris particle)
-const WIDTH = 64;
-
-let container, stats;
-let camera, scene, renderer, geometry;
-
-const PARTICLES = WIDTH * WIDTH;
-
-let gpuCompute;
-let velocityVariable;
-let positionVariable;
-let velocityUniforms;
-let particleUniforms;
-let effectController;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 5, 15000);
-    camera.position.y = 120;
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 100;
-    controls.maxDistance = 1000;
-
-    effectController = {
-        // Can be changed dynamically
-        gravityConstant: 100.0,
-        density: 0.45,
-
-        // Must restart simulation
-        radius: 300,
-        height: 8,
-        exponent: 0.4,
-        maxMass: 15.0,
-        velocity: 70,
-        velocityExponent: 0.2,
-        randVelocity: 0.001,
-    };
-
-    initComputeRenderer();
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    initGUI();
-
-    initProtoplanets();
-
-    dynamicValuesChanger();
-}
-
-function initComputeRenderer() {
-    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
-
-    const dtPosition = gpuCompute.createTexture();
-    const dtVelocity = gpuCompute.createTexture();
-
-    fillTextures(dtPosition, dtVelocity);
-
-    velocityVariable = gpuCompute.addVariable(
-        'textureVelocity',
-        document.getElementById('computeShaderVelocity').textContent,
-        dtVelocity,
-    );
-    positionVariable = gpuCompute.addVariable(
-        'texturePosition',
-        document.getElementById('computeShaderPosition').textContent,
-        dtPosition,
-    );
-
-    gpuCompute.setVariableDependencies(velocityVariable, [positionVariable, velocityVariable]);
-    gpuCompute.setVariableDependencies(positionVariable, [positionVariable, velocityVariable]);
-
-    velocityUniforms = velocityVariable.material.uniforms;
-
-    velocityUniforms['gravityConstant'] = { value: 0.0 };
-    velocityUniforms['density'] = { value: 0.0 };
-
-    const error = gpuCompute.init();
-
-    if (error !== null) {
-        console.error(error);
-    }
-}
-
-function restartSimulation() {
-    const dtPosition = gpuCompute.createTexture();
-    const dtVelocity = gpuCompute.createTexture();
-
-    fillTextures(dtPosition, dtVelocity);
-
-    gpuCompute.renderTexture(dtPosition, positionVariable.renderTargets[0]);
-    gpuCompute.renderTexture(dtPosition, positionVariable.renderTargets[1]);
-    gpuCompute.renderTexture(dtVelocity, velocityVariable.renderTargets[0]);
-    gpuCompute.renderTexture(dtVelocity, velocityVariable.renderTargets[1]);
-}
-
-function initProtoplanets() {
-    geometry = new THREE.BufferGeometry();
-
-    const positions = new Float32Array(PARTICLES * 3);
-    let p = 0;
-
-    for (let i = 0; i < PARTICLES; i++) {
-        positions[p++] = (Math.random() * 2 - 1) * effectController.radius;
-        positions[p++] = 0; //( Math.random() * 2 - 1 ) * effectController.radius;
-        positions[p++] = (Math.random() * 2 - 1) * effectController.radius;
-    }
-
-    const uvs = new Float32Array(PARTICLES * 2);
-    p = 0;
-
-    for (let j = 0; j < WIDTH; j++) {
-        for (let i = 0; i < WIDTH; i++) {
-            uvs[p++] = i / (WIDTH - 1);
-            uvs[p++] = j / (WIDTH - 1);
-        }
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
-
-    particleUniforms = {
-        texturePosition: { value: null },
-        textureVelocity: { value: null },
-        cameraConstant: { value: getCameraConstant(camera) },
-        density: { value: 0.0 },
-    };
-
-    // THREE.ShaderMaterial
-    const material = new THREE.ShaderMaterial({
-        uniforms: particleUniforms,
-        vertexShader: document.getElementById('particleVertexShader').textContent,
-        fragmentShader: document.getElementById('particleFragmentShader').textContent,
-    });
-
-    const particles = new THREE.Points(geometry, material);
-    particles.matrixAutoUpdate = false;
-    particles.updateMatrix();
-
-    scene.add(particles);
-}
-
-function fillTextures(texturePosition, textureVelocity) {
-    const posArray = texturePosition.image.data;
-    const velArray = textureVelocity.image.data;
-
-    const radius = effectController.radius;
-    const height = effectController.height;
-    const exponent = effectController.exponent;
-    const maxMass = (effectController.maxMass * 1024) / PARTICLES;
-    const maxVel = effectController.velocity;
-    const velExponent = effectController.velocityExponent;
-    const randVel = effectController.randVelocity;
-
-    for (let k = 0, kl = posArray.length; k < kl; k += 4) {
-        // Position
-        let x, z, rr;
-
-        do {
-            x = Math.random() * 2 - 1;
-            z = Math.random() * 2 - 1;
-            rr = x * x + z * z;
-        } while (rr > 1);
-
-        rr = Math.sqrt(rr);
-
-        const rExp = radius * Math.pow(rr, exponent);
-
-        // Velocity
-        const vel = maxVel * Math.pow(rr, velExponent);
-
-        const vx = vel * z + (Math.random() * 2 - 1) * randVel;
-        const vy = (Math.random() * 2 - 1) * randVel * 0.05;
-        const vz = -vel * x + (Math.random() * 2 - 1) * randVel;
-
-        x *= rExp;
-        z *= rExp;
-        const y = (Math.random() * 2 - 1) * height;
-
-        const mass = Math.random() * maxMass + 1;
-
-        // Fill in texture values
-        posArray[k + 0] = x;
-        posArray[k + 1] = y;
-        posArray[k + 2] = z;
-        posArray[k + 3] = 1;
-
-        velArray[k + 0] = vx;
-        velArray[k + 1] = vy;
-        velArray[k + 2] = vz;
-        velArray[k + 3] = mass;
-    }
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    particleUniforms['cameraConstant'].value = getCameraConstant(camera);
-}
-
-function dynamicValuesChanger() {
-    velocityUniforms['gravityConstant'].value = effectController.gravityConstant;
-    velocityUniforms['density'].value = effectController.density;
-    particleUniforms['density'].value = effectController.density;
-}
-
-function initGUI() {
-    const gui = new GUI({ width: 280 });
-
-    const folder1 = gui.addFolder('Dynamic parameters');
-
-    folder1.add(effectController, 'gravityConstant', 0.0, 1000.0, 0.05).onChange(dynamicValuesChanger);
-    folder1.add(effectController, 'density', 0.0, 10.0, 0.001).onChange(dynamicValuesChanger);
-
-    const folder2 = gui.addFolder('Static parameters');
-
-    folder2.add(effectController, 'radius', 10.0, 1000.0, 1.0);
-    folder2.add(effectController, 'height', 0.0, 50.0, 0.01);
-    folder2.add(effectController, 'exponent', 0.0, 2.0, 0.001);
-    folder2.add(effectController, 'maxMass', 1.0, 50.0, 0.1);
-    folder2.add(effectController, 'velocity', 0.0, 150.0, 0.1);
-    folder2.add(effectController, 'velocityExponent', 0.0, 1.0, 0.01);
-    folder2.add(effectController, 'randVelocity', 0.0, 50.0, 0.1);
-
-    const buttonRestart = {
-        restartSimulation: function () {
-            restartSimulation();
-        },
-    };
-
-    folder2.add(buttonRestart, 'restartSimulation');
-
-    folder1.open();
-    folder2.open();
-}
-
-function getCameraConstant(camera) {
-    return window.innerHeight / (Math.tan(THREE.MathUtils.DEG2RAD * 0.5 * camera.fov) / camera.zoom);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    gpuCompute.compute();
-
-    particleUniforms['texturePosition'].value = gpuCompute.getCurrentRenderTarget(positionVariable).texture;
-    particleUniforms['textureVelocity'].value = gpuCompute.getCurrentRenderTarget(velocityVariable).texture;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_gpgpu_water.ts b/examples-testing/examples/webgl_gpgpu_water.ts
deleted file mode 100644
index 00c32f229..000000000
--- a/examples-testing/examples/webgl_gpgpu_water.ts
+++ /dev/null
@@ -1,397 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
-import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
-
-// Texture width for simulation
-const WIDTH = 128;
-
-// Water size in system units
-const BOUNDS = 512;
-const BOUNDS_HALF = BOUNDS * 0.5;
-
-let container, stats;
-let camera, scene, renderer;
-let mouseMoved = false;
-const mouseCoords = new THREE.Vector2();
-const raycaster = new THREE.Raycaster();
-
-let waterMesh;
-let meshRay;
-let gpuCompute;
-let heightmapVariable;
-let waterUniforms;
-let smoothShader;
-let readWaterLevelShader;
-let readWaterLevelRenderTarget;
-let readWaterLevelImage;
-const waterNormal = new THREE.Vector3();
-
-const NUM_SPHERES = 5;
-const spheres = [];
-let spheresEnabled = true;
-
-const simplex = new SimplexNoise();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
-    camera.position.set(0, 200, 350);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-
-    const sun = new THREE.DirectionalLight(0xffffff, 3.0);
-    sun.position.set(300, 400, 175);
-    scene.add(sun);
-
-    const sun2 = new THREE.DirectionalLight(0x40a040, 2.0);
-    sun2.position.set(-100, 350, -200);
-    scene.add(sun2);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointermove', onPointerMove);
-
-    document.addEventListener('keydown', function (event) {
-        // W Pressed: Toggle wireframe
-        if (event.keyCode === 87) {
-            waterMesh.material.wireframe = !waterMesh.material.wireframe;
-            waterMesh.material.needsUpdate = true;
-        }
-    });
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    const effectController = {
-        mouseSize: 20.0,
-        viscosity: 0.98,
-        spheresEnabled: spheresEnabled,
-    };
-
-    const valuesChanger = function () {
-        heightmapVariable.material.uniforms['mouseSize'].value = effectController.mouseSize;
-        heightmapVariable.material.uniforms['viscosityConstant'].value = effectController.viscosity;
-        spheresEnabled = effectController.spheresEnabled;
-        for (let i = 0; i < NUM_SPHERES; i++) {
-            if (spheres[i]) {
-                spheres[i].visible = spheresEnabled;
-            }
-        }
-    };
-
-    gui.add(effectController, 'mouseSize', 1.0, 100.0, 1.0).onChange(valuesChanger);
-    gui.add(effectController, 'viscosity', 0.9, 0.999, 0.001).onChange(valuesChanger);
-    gui.add(effectController, 'spheresEnabled').onChange(valuesChanger);
-    const buttonSmooth = {
-        smoothWater: function () {
-            smoothWater();
-        },
-    };
-    gui.add(buttonSmooth, 'smoothWater');
-
-    initWater();
-
-    createSpheres();
-
-    valuesChanger();
-}
-
-function initWater() {
-    const materialColor = 0x0040c0;
-
-    const geometry = new THREE.PlaneGeometry(BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1);
-
-    // material: make a THREE.ShaderMaterial clone of THREE.MeshPhongMaterial, with customized vertex shader
-    const material = new THREE.ShaderMaterial({
-        uniforms: THREE.UniformsUtils.merge([
-            THREE.ShaderLib['phong'].uniforms,
-            {
-                heightmap: { value: null },
-            },
-        ]),
-        vertexShader: document.getElementById('waterVertexShader').textContent,
-        fragmentShader: THREE.ShaderChunk['meshphong_frag'],
-    });
-
-    material.lights = true;
-
-    // Material attributes from THREE.MeshPhongMaterial
-    // Sets the uniforms with the material values
-    material.uniforms['diffuse'].value = new THREE.Color(materialColor);
-    material.uniforms['specular'].value = new THREE.Color(0x111111);
-    material.uniforms['shininess'].value = Math.max(50, 1e-4);
-    material.uniforms['opacity'].value = material.opacity;
-
-    // Defines
-    material.defines.WIDTH = WIDTH.toFixed(1);
-    material.defines.BOUNDS = BOUNDS.toFixed(1);
-
-    waterUniforms = material.uniforms;
-
-    waterMesh = new THREE.Mesh(geometry, material);
-    waterMesh.rotation.x = -Math.PI / 2;
-    waterMesh.matrixAutoUpdate = false;
-    waterMesh.updateMatrix();
-
-    scene.add(waterMesh);
-
-    // THREE.Mesh just for mouse raycasting
-    const geometryRay = new THREE.PlaneGeometry(BOUNDS, BOUNDS, 1, 1);
-    meshRay = new THREE.Mesh(geometryRay, new THREE.MeshBasicMaterial({ color: 0xffffff, visible: false }));
-    meshRay.rotation.x = -Math.PI / 2;
-    meshRay.matrixAutoUpdate = false;
-    meshRay.updateMatrix();
-    scene.add(meshRay);
-
-    // Creates the gpu computation class and sets it up
-
-    gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
-
-    const heightmap0 = gpuCompute.createTexture();
-
-    fillTexture(heightmap0);
-
-    heightmapVariable = gpuCompute.addVariable(
-        'heightmap',
-        document.getElementById('heightmapFragmentShader').textContent,
-        heightmap0,
-    );
-
-    gpuCompute.setVariableDependencies(heightmapVariable, [heightmapVariable]);
-
-    heightmapVariable.material.uniforms['mousePos'] = { value: new THREE.Vector2(10000, 10000) };
-    heightmapVariable.material.uniforms['mouseSize'] = { value: 20.0 };
-    heightmapVariable.material.uniforms['viscosityConstant'] = { value: 0.98 };
-    heightmapVariable.material.uniforms['heightCompensation'] = { value: 0 };
-    heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed(1);
-
-    const error = gpuCompute.init();
-    if (error !== null) {
-        console.error(error);
-    }
-
-    // Create compute shader to smooth the water surface and velocity
-    smoothShader = gpuCompute.createShaderMaterial(document.getElementById('smoothFragmentShader').textContent, {
-        smoothTexture: { value: null },
-    });
-
-    // Create compute shader to read water level
-    readWaterLevelShader = gpuCompute.createShaderMaterial(
-        document.getElementById('readWaterLevelFragmentShader').textContent,
-        {
-            point1: { value: new THREE.Vector2() },
-            levelTexture: { value: null },
-        },
-    );
-    readWaterLevelShader.defines.WIDTH = WIDTH.toFixed(1);
-    readWaterLevelShader.defines.BOUNDS = BOUNDS.toFixed(1);
-
-    // Create a 4x1 pixel image and a render target (Uint8, 4 channels, 1 byte per channel) to read water height and orientation
-    readWaterLevelImage = new Uint8Array(4 * 1 * 4);
-
-    readWaterLevelRenderTarget = new THREE.WebGLRenderTarget(4, 1, {
-        wrapS: THREE.ClampToEdgeWrapping,
-        wrapT: THREE.ClampToEdgeWrapping,
-        minFilter: THREE.NearestFilter,
-        magFilter: THREE.NearestFilter,
-        format: THREE.RGBAFormat,
-        type: THREE.UnsignedByteType,
-        depthBuffer: false,
-    });
-}
-
-function fillTexture(texture) {
-    const waterMaxHeight = 10;
-
-    function noise(x, y) {
-        let multR = waterMaxHeight;
-        let mult = 0.025;
-        let r = 0;
-        for (let i = 0; i < 15; i++) {
-            r += multR * simplex.noise(x * mult, y * mult);
-            multR *= 0.53 + 0.025 * i;
-            mult *= 1.25;
-        }
-
-        return r;
-    }
-
-    const pixels = texture.image.data;
-
-    let p = 0;
-    for (let j = 0; j < WIDTH; j++) {
-        for (let i = 0; i < WIDTH; i++) {
-            const x = (i * 128) / WIDTH;
-            const y = (j * 128) / WIDTH;
-
-            pixels[p + 0] = noise(x, y);
-            pixels[p + 1] = pixels[p + 0];
-            pixels[p + 2] = 0;
-            pixels[p + 3] = 1;
-
-            p += 4;
-        }
-    }
-}
-
-function smoothWater() {
-    const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
-    const alternateRenderTarget = gpuCompute.getAlternateRenderTarget(heightmapVariable);
-
-    for (let i = 0; i < 10; i++) {
-        smoothShader.uniforms['smoothTexture'].value = currentRenderTarget.texture;
-        gpuCompute.doRenderTarget(smoothShader, alternateRenderTarget);
-
-        smoothShader.uniforms['smoothTexture'].value = alternateRenderTarget.texture;
-        gpuCompute.doRenderTarget(smoothShader, currentRenderTarget);
-    }
-}
-
-function createSpheres() {
-    const sphereTemplate = new THREE.Mesh(
-        new THREE.SphereGeometry(4, 24, 12),
-        new THREE.MeshPhongMaterial({ color: 0xffff00 }),
-    );
-
-    for (let i = 0; i < NUM_SPHERES; i++) {
-        let sphere = sphereTemplate;
-        if (i < NUM_SPHERES - 1) {
-            sphere = sphereTemplate.clone();
-        }
-
-        sphere.position.x = (Math.random() - 0.5) * BOUNDS * 0.7;
-        sphere.position.z = (Math.random() - 0.5) * BOUNDS * 0.7;
-
-        sphere.userData.velocity = new THREE.Vector3();
-
-        scene.add(sphere);
-
-        spheres[i] = sphere;
-    }
-}
-
-function sphereDynamics() {
-    const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
-
-    readWaterLevelShader.uniforms['levelTexture'].value = currentRenderTarget.texture;
-
-    for (let i = 0; i < NUM_SPHERES; i++) {
-        const sphere = spheres[i];
-
-        if (sphere) {
-            // Read water level and orientation
-            const u = (0.5 * sphere.position.x) / BOUNDS_HALF + 0.5;
-            const v = 1 - ((0.5 * sphere.position.z) / BOUNDS_HALF + 0.5);
-            readWaterLevelShader.uniforms['point1'].value.set(u, v);
-            gpuCompute.doRenderTarget(readWaterLevelShader, readWaterLevelRenderTarget);
-
-            renderer.readRenderTargetPixels(readWaterLevelRenderTarget, 0, 0, 4, 1, readWaterLevelImage);
-            const pixels = new Float32Array(readWaterLevelImage.buffer);
-
-            // Get orientation
-            waterNormal.set(pixels[1], 0, -pixels[2]);
-
-            const pos = sphere.position;
-
-            // Set height
-            pos.y = pixels[0];
-
-            // Move sphere
-            waterNormal.multiplyScalar(0.1);
-            sphere.userData.velocity.add(waterNormal);
-            sphere.userData.velocity.multiplyScalar(0.998);
-            pos.add(sphere.userData.velocity);
-
-            if (pos.x < -BOUNDS_HALF) {
-                pos.x = -BOUNDS_HALF + 0.001;
-                sphere.userData.velocity.x *= -0.3;
-            } else if (pos.x > BOUNDS_HALF) {
-                pos.x = BOUNDS_HALF - 0.001;
-                sphere.userData.velocity.x *= -0.3;
-            }
-
-            if (pos.z < -BOUNDS_HALF) {
-                pos.z = -BOUNDS_HALF + 0.001;
-                sphere.userData.velocity.z *= -0.3;
-            } else if (pos.z > BOUNDS_HALF) {
-                pos.z = BOUNDS_HALF - 0.001;
-                sphere.userData.velocity.z *= -0.3;
-            }
-        }
-    }
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function setMouseCoords(x, y) {
-    mouseCoords.set((x / renderer.domElement.clientWidth) * 2 - 1, -(y / renderer.domElement.clientHeight) * 2 + 1);
-    mouseMoved = true;
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    setMouseCoords(event.clientX, event.clientY);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    // Set uniforms: mouse interaction
-    const uniforms = heightmapVariable.material.uniforms;
-    if (mouseMoved) {
-        raycaster.setFromCamera(mouseCoords, camera);
-
-        const intersects = raycaster.intersectObject(meshRay);
-
-        if (intersects.length > 0) {
-            const point = intersects[0].point;
-            uniforms['mousePos'].value.set(point.x, point.z);
-        } else {
-            uniforms['mousePos'].value.set(10000, 10000);
-        }
-
-        mouseMoved = false;
-    } else {
-        uniforms['mousePos'].value.set(10000, 10000);
-    }
-
-    // Do the gpu computation
-    gpuCompute.compute();
-
-    if (spheresEnabled) {
-        sphereDynamics();
-    }
-
-    // Get compute output in custom uniform
-    waterUniforms['heightmap'].value = gpuCompute.getCurrentRenderTarget(heightmapVariable).texture;
-
-    // Render
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_helpers.ts b/examples-testing/examples/webgl_helpers.ts
deleted file mode 100644
index a8c3b9773..000000000
--- a/examples-testing/examples/webgl_helpers.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { VertexNormalsHelper } from 'three/addons/helpers/VertexNormalsHelper.js';
-import { VertexTangentsHelper } from 'three/addons/helpers/VertexTangentsHelper.js';
-
-let scene, renderer;
-let camera, light;
-let vnh;
-let vth;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-
-    light = new THREE.PointLight();
-    light.position.set(200, 100, 150);
-    scene.add(light);
-
-    scene.add(new THREE.PointLightHelper(light, 15));
-
-    const gridHelper = new THREE.GridHelper(400, 40, 0x0000ff, 0x808080);
-    gridHelper.position.y = -150;
-    gridHelper.position.x = -150;
-    scene.add(gridHelper);
-
-    const polarGridHelper = new THREE.PolarGridHelper(200, 16, 8, 64, 0x0000ff, 0x808080);
-    polarGridHelper.position.y = -150;
-    polarGridHelper.position.x = 200;
-    scene.add(polarGridHelper);
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-
-        mesh.geometry.computeTangents(); // generates bad data due to degenerate UVs
-
-        const group = new THREE.Group();
-        group.scale.multiplyScalar(50);
-        scene.add(group);
-
-        // To make sure that the matrixWorld is up to date for the boxhelpers
-        group.updateMatrixWorld(true);
-
-        group.add(mesh);
-
-        vnh = new VertexNormalsHelper(mesh, 5);
-        scene.add(vnh);
-
-        vth = new VertexTangentsHelper(mesh, 5);
-        scene.add(vth);
-
-        scene.add(new THREE.BoxHelper(mesh));
-
-        const wireframe = new THREE.WireframeGeometry(mesh.geometry);
-        let line = new THREE.LineSegments(wireframe);
-        line.material.depthTest = false;
-        line.material.opacity = 0.25;
-        line.material.transparent = true;
-        line.position.x = 4;
-        group.add(line);
-        scene.add(new THREE.BoxHelper(line));
-
-        const edges = new THREE.EdgesGeometry(mesh.geometry);
-        line = new THREE.LineSegments(edges);
-        line.material.depthTest = false;
-        line.material.opacity = 0.25;
-        line.material.transparent = true;
-        line.position.x = -4;
-        group.add(line);
-        scene.add(new THREE.BoxHelper(line));
-
-        scene.add(new THREE.BoxHelper(group));
-        scene.add(new THREE.BoxHelper(scene));
-    });
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = -performance.now() * 0.0003;
-
-    camera.position.x = 400 * Math.cos(time);
-    camera.position.z = 400 * Math.sin(time);
-    camera.lookAt(scene.position);
-
-    light.position.x = Math.sin(time * 1.7) * 300;
-    light.position.y = Math.cos(time * 1.5) * 400;
-    light.position.z = Math.cos(time * 1.3) * 300;
-
-    if (vnh) vnh.update();
-    if (vth) vth.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_instancing_dynamic.ts b/examples-testing/examples/webgl_instancing_dynamic.ts
deleted file mode 100644
index 88562fc5a..000000000
--- a/examples-testing/examples/webgl_instancing_dynamic.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats;
-
-let mesh;
-const amount = parseInt(window.location.search.slice(1)) || 10;
-const count = Math.pow(amount, 3);
-const dummy = new THREE.Object3D();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(amount * 0.9, amount * 0.9, amount * 0.9);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-
-    const loader = new THREE.BufferGeometryLoader();
-    loader.load('models/json/suzanne_buffergeometry.json', function (geometry) {
-        geometry.computeVertexNormals();
-        geometry.scale(0.5, 0.5, 0.5);
-
-        const material = new THREE.MeshNormalMaterial();
-        // check overdraw
-        // let material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } );
-
-        mesh = new THREE.InstancedMesh(geometry, material, count);
-        mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // will be updated every frame
-        scene.add(mesh);
-
-        //
-
-        const gui = new GUI();
-        gui.add(mesh, 'count', 0, count);
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    if (mesh) {
-        const time = Date.now() * 0.001;
-
-        mesh.rotation.x = Math.sin(time / 4);
-        mesh.rotation.y = Math.sin(time / 2);
-
-        let i = 0;
-        const offset = (amount - 1) / 2;
-
-        for (let x = 0; x < amount; x++) {
-            for (let y = 0; y < amount; y++) {
-                for (let z = 0; z < amount; z++) {
-                    dummy.position.set(offset - x, offset - y, offset - z);
-                    dummy.rotation.y = Math.sin(x / 4 + time) + Math.sin(y / 4 + time) + Math.sin(z / 4 + time);
-                    dummy.rotation.z = dummy.rotation.y * 2;
-
-                    dummy.updateMatrix();
-
-                    mesh.setMatrixAt(i++, dummy.matrix);
-                }
-            }
-        }
-
-        mesh.instanceMatrix.needsUpdate = true;
-        mesh.computeBoundingSphere();
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_instancing_morph.ts b/examples-testing/examples/webgl_instancing_morph.ts
deleted file mode 100644
index 8686a75b9..000000000
--- a/examples-testing/examples/webgl_instancing_morph.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats, mesh, mixer, dummy;
-
-const offset = 5000;
-
-const timeOffsets = new Float32Array(1024);
-
-for (let i = 0; i < 1024; i++) {
-    timeOffsets[i] = Math.random() * 3;
-}
-
-const clock = new THREE.Clock(true);
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 10000);
-
-    scene = new THREE.Scene();
-
-    scene.background = new THREE.Color(0x99ddff);
-
-    scene.fog = new THREE.Fog(0x99ddff, 5000, 10000);
-
-    const light = new THREE.DirectionalLight(0xffffff, 1);
-
-    light.position.set(200, 1000, 50);
-
-    light.castShadow = true;
-
-    light.shadow.camera.left = -5000;
-    light.shadow.camera.right = 5000;
-    light.shadow.camera.top = 5000;
-    light.shadow.camera.bottom = -5000;
-    light.shadow.camera.far = 2000;
-
-    light.shadow.bias = -0.01;
-
-    light.shadow.camera.updateProjectionMatrix();
-
-    scene.add(light);
-
-    const hemi = new THREE.HemisphereLight(0x99ddff, 0x669933, 1 / 3);
-
-    scene.add(hemi);
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(1000000, 1000000),
-        new THREE.MeshStandardMaterial({ color: 0x669933, depthWrite: true }),
-    );
-
-    ground.rotation.x = -Math.PI / 2;
-
-    ground.receiveShadow = true;
-
-    scene.add(ground);
-
-    const loader = new GLTFLoader();
-
-    loader.load('models/gltf/Horse.glb', function (glb) {
-        dummy = glb.scene.children[0];
-
-        mesh = new THREE.InstancedMesh(dummy.geometry, dummy.material, 1024);
-
-        mesh.castShadow = true;
-
-        for (let x = 0, i = 0; x < 32; x++) {
-            for (let y = 0; y < 32; y++) {
-                dummy.position.set(offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y);
-
-                dummy.updateMatrix();
-
-                mesh.setMatrixAt(i, dummy.matrix);
-
-                mesh.setColorAt(i, new THREE.Color(`hsl(${Math.random() * 360}, 50%, 66%)`));
-
-                i++;
-            }
-        }
-
-        scene.add(mesh);
-
-        mixer = new THREE.AnimationMixer(glb.scene);
-
-        const action = mixer.clipAction(glb.animations[0]);
-
-        action.play();
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.VSMShadowMap;
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const time = clock.getElapsedTime();
-
-    const r = 3000;
-    camera.position.set(Math.sin(time / 10) * r, 1500 + 1000 * Math.cos(time / 5), Math.cos(time / 10) * r);
-    camera.lookAt(0, 0, 0);
-
-    if (mesh) {
-        for (let i = 0; i < 1024; i++) {
-            mixer.setTime(time + timeOffsets[i]);
-
-            mesh.setMorphAt(i, dummy);
-        }
-
-        mesh.morphTexture.needsUpdate = true;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_instancing_performance.ts b/examples-testing/examples/webgl_instancing_performance.ts
deleted file mode 100644
index bf1deabad..000000000
--- a/examples-testing/examples/webgl_instancing_performance.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let container, stats, gui, guiStatsEl;
-let camera, controls, scene, renderer, material;
-
-// gui
-
-const Method = {
-    INSTANCED: 'INSTANCED',
-    MERGED: 'MERGED',
-    NAIVE: 'NAIVE',
-};
-
-const api = {
-    method: Method.INSTANCED,
-    count: 1000,
-};
-
-//
-
-init();
-initMesh();
-
-//
-
-function clean() {
-    const meshes = [];
-
-    scene.traverse(function (object) {
-        if (object.isMesh) meshes.push(object);
-    });
-
-    for (let i = 0; i < meshes.length; i++) {
-        const mesh = meshes[i];
-        mesh.material.dispose();
-        mesh.geometry.dispose();
-
-        scene.remove(mesh);
-    }
-}
-
-const randomizeMatrix = (function () {
-    const position = new THREE.Vector3();
-    const quaternion = new THREE.Quaternion();
-    const scale = new THREE.Vector3();
-
-    return function (matrix) {
-        position.x = Math.random() * 40 - 20;
-        position.y = Math.random() * 40 - 20;
-        position.z = Math.random() * 40 - 20;
-
-        quaternion.random();
-
-        scale.x = scale.y = scale.z = Math.random() * 1;
-
-        matrix.compose(position, quaternion, scale);
-    };
-})();
-
-function initMesh() {
-    clean();
-
-    // make instances
-    new THREE.BufferGeometryLoader().setPath('models/json/').load('suzanne_buffergeometry.json', function (geometry) {
-        material = new THREE.MeshNormalMaterial();
-
-        geometry.computeVertexNormals();
-
-        console.time(api.method + ' (build)');
-
-        switch (api.method) {
-            case Method.INSTANCED:
-                makeInstanced(geometry);
-                break;
-
-            case Method.MERGED:
-                makeMerged(geometry);
-                break;
-
-            case Method.NAIVE:
-                makeNaive(geometry);
-                break;
-        }
-
-        console.timeEnd(api.method + ' (build)');
-    });
-}
-
-function makeInstanced(geometry) {
-    const matrix = new THREE.Matrix4();
-    const mesh = new THREE.InstancedMesh(geometry, material, api.count);
-
-    for (let i = 0; i < api.count; i++) {
-        randomizeMatrix(matrix);
-        mesh.setMatrixAt(i, matrix);
-    }
-
-    scene.add(mesh);
-
-    //
-
-    const geometryByteLength = getGeometryByteLength(geometry);
-
-    guiStatsEl.innerHTML = [
-        '<i>GPU draw calls</i>: 1',
-        '<i>GPU memory</i>: ' + formatBytes(api.count * 16 + geometryByteLength, 2),
-    ].join('<br/>');
-}
-
-function makeMerged(geometry) {
-    const geometries = [];
-    const matrix = new THREE.Matrix4();
-
-    for (let i = 0; i < api.count; i++) {
-        randomizeMatrix(matrix);
-
-        const instanceGeometry = geometry.clone();
-        instanceGeometry.applyMatrix4(matrix);
-
-        geometries.push(instanceGeometry);
-    }
-
-    const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
-
-    scene.add(new THREE.Mesh(mergedGeometry, material));
-
-    //
-
-    guiStatsEl.innerHTML = [
-        '<i>GPU draw calls</i>: 1',
-        '<i>GPU memory</i>: ' + formatBytes(getGeometryByteLength(mergedGeometry), 2),
-    ].join('<br/>');
-}
-
-function makeNaive(geometry) {
-    const matrix = new THREE.Matrix4();
-
-    for (let i = 0; i < api.count; i++) {
-        randomizeMatrix(matrix);
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.applyMatrix4(matrix);
-
-        scene.add(mesh);
-    }
-
-    //
-
-    const geometryByteLength = getGeometryByteLength(geometry);
-
-    guiStatsEl.innerHTML = [
-        '<i>GPU draw calls</i>: ' + api.count,
-        '<i>GPU memory</i>: ' + formatBytes(api.count * 16 + geometryByteLength, 2),
-    ].join('<br/>');
-}
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
-    camera.position.z = 30;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-
-    // stats
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // gui
-
-    gui = new GUI();
-    gui.add(api, 'method', Method).onChange(initMesh);
-    gui.add(api, 'count', 1, 10000).step(1).onChange(initMesh);
-
-    const perfFolder = gui.addFolder('Performance');
-
-    guiStatsEl = document.createElement('div');
-    guiStatsEl.classList.add('gui-stats');
-
-    perfFolder.$children.appendChild(guiStatsEl);
-    perfFolder.open();
-
-    // listeners
-
-    window.addEventListener('resize', onWindowResize);
-
-    Object.assign(window, { scene });
-}
-
-//
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
-
-//
-
-function getGeometryByteLength(geometry) {
-    let total = 0;
-
-    if (geometry.index) total += geometry.index.array.byteLength;
-
-    for (const name in geometry.attributes) {
-        total += geometry.attributes[name].array.byteLength;
-    }
-
-    return total;
-}
-
-// Source: https://stackoverflow.com/a/18650828/1314762
-function formatBytes(bytes, decimals) {
-    if (bytes === 0) return '0 bytes';
-
-    const k = 1024;
-    const dm = decimals < 0 ? 0 : decimals;
-    const sizes = ['bytes', 'KB', 'MB'];
-
-    const i = Math.floor(Math.log(bytes) / Math.log(k));
-
-    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-}
diff --git a/examples-testing/examples/webgl_instancing_raycast.ts b/examples-testing/examples/webgl_instancing_raycast.ts
deleted file mode 100644
index 371ea070b..000000000
--- a/examples-testing/examples/webgl_instancing_raycast.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, controls, stats;
-
-let mesh;
-const amount = parseInt(window.location.search.slice(1)) || 10;
-const count = Math.pow(amount, 3);
-
-const raycaster = new THREE.Raycaster();
-const mouse = new THREE.Vector2(1, 1);
-
-const color = new THREE.Color();
-const white = new THREE.Color().setHex(0xffffff);
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(amount, amount, amount);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-
-    const light = new THREE.HemisphereLight(0xffffff, 0x888888, 3);
-    light.position.set(0, 1, 0);
-    scene.add(light);
-
-    const geometry = new THREE.IcosahedronGeometry(0.5, 3);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff });
-
-    mesh = new THREE.InstancedMesh(geometry, material, count);
-
-    let i = 0;
-    const offset = (amount - 1) / 2;
-
-    const matrix = new THREE.Matrix4();
-
-    for (let x = 0; x < amount; x++) {
-        for (let y = 0; y < amount; y++) {
-            for (let z = 0; z < amount; z++) {
-                matrix.setPosition(offset - x, offset - y, offset - z);
-
-                mesh.setMatrixAt(i, matrix);
-                mesh.setColorAt(i, color);
-
-                i++;
-            }
-        }
-    }
-
-    scene.add(mesh);
-
-    //
-
-    const gui = new GUI();
-    gui.add(mesh, 'count', 0, count);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.enableZoom = false;
-    controls.enablePan = false;
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('mousemove', onMouseMove);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onMouseMove(event) {
-    event.preventDefault();
-
-    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-function animate() {
-    controls.update();
-
-    raycaster.setFromCamera(mouse, camera);
-
-    const intersection = raycaster.intersectObject(mesh);
-
-    if (intersection.length > 0) {
-        const instanceId = intersection[0].instanceId;
-
-        mesh.getColorAt(instanceId, color);
-
-        if (color.equals(white)) {
-            mesh.setColorAt(instanceId, color.setHex(Math.random() * 0xffffff));
-
-            mesh.instanceColor.needsUpdate = true;
-        }
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_instancing_scatter.ts b/examples-testing/examples/webgl_instancing_scatter.ts
deleted file mode 100644
index fc3b9cc9f..000000000
--- a/examples-testing/examples/webgl_instancing_scatter.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-import * as THREE from 'three';
-
-import { MeshSurfaceSampler } from 'three/addons/math/MeshSurfaceSampler.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats;
-
-const api = {
-    count: 2000,
-    distribution: 'random',
-    resample: resample,
-    surfaceColor: 0xfff784,
-    backgroundColor: 0xe39469,
-};
-
-let stemMesh, blossomMesh;
-let stemGeometry, blossomGeometry;
-let stemMaterial, blossomMaterial;
-
-let sampler;
-const count = api.count;
-const ages = new Float32Array(count);
-const scales = new Float32Array(count);
-const dummy = new THREE.Object3D();
-
-const _position = new THREE.Vector3();
-const _normal = new THREE.Vector3();
-const _scale = new THREE.Vector3();
-
-// let surfaceGeometry = new THREE.BoxGeometry( 10, 10, 10 ).toNonIndexed();
-const surfaceGeometry = new THREE.TorusKnotGeometry(10, 3, 100, 16).toNonIndexed();
-const surfaceMaterial = new THREE.MeshLambertMaterial({ color: api.surfaceColor, wireframe: false });
-const surface = new THREE.Mesh(surfaceGeometry, surfaceMaterial);
-
-// Source: https://gist.github.com/gre/1650294
-const easeOutCubic = function (t) {
-    return --t * t * t + 1;
-};
-
-// Scaling curve causes particles to grow quickly, ease gradually into full scale, then
-// disappear quickly. More of the particle's lifetime is spent around full scale.
-const scaleCurve = function (t) {
-    return Math.abs(easeOutCubic((t > 0.5 ? 1 - t : t) * 2));
-};
-
-const loader = new GLTFLoader();
-
-loader.load('./models/gltf/Flower/Flower.glb', function (gltf) {
-    const _stemMesh = gltf.scene.getObjectByName('Stem');
-    const _blossomMesh = gltf.scene.getObjectByName('Blossom');
-
-    stemGeometry = _stemMesh.geometry.clone();
-    blossomGeometry = _blossomMesh.geometry.clone();
-
-    const defaultTransform = new THREE.Matrix4()
-        .makeRotationX(Math.PI)
-        .multiply(new THREE.Matrix4().makeScale(7, 7, 7));
-
-    stemGeometry.applyMatrix4(defaultTransform);
-    blossomGeometry.applyMatrix4(defaultTransform);
-
-    stemMaterial = _stemMesh.material;
-    blossomMaterial = _blossomMesh.material;
-
-    stemMesh = new THREE.InstancedMesh(stemGeometry, stemMaterial, count);
-    blossomMesh = new THREE.InstancedMesh(blossomGeometry, blossomMaterial, count);
-
-    // Assign random colors to the blossoms.
-    const color = new THREE.Color();
-    const blossomPalette = [0xf20587, 0xf2d479, 0xf2c879, 0xf2b077, 0xf24405];
-
-    for (let i = 0; i < count; i++) {
-        color.setHex(blossomPalette[Math.floor(Math.random() * blossomPalette.length)]);
-        blossomMesh.setColorAt(i, color);
-    }
-
-    // Instance matrices will be updated every frame.
-    stemMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
-    blossomMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
-
-    resample();
-
-    init();
-});
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(25, 25, 25);
-    camera.lookAt(0, 0, 0);
-
-    //
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(api.backgroundColor);
-
-    const pointLight = new THREE.PointLight(0xaa8899, 2.5, 0, 0);
-    pointLight.position.set(50, -25, 75);
-    scene.add(pointLight);
-
-    scene.add(new THREE.AmbientLight(0xffffff, 3));
-
-    //
-
-    scene.add(stemMesh);
-    scene.add(blossomMesh);
-
-    scene.add(surface);
-
-    //
-
-    const gui = new GUI();
-    gui.add(api, 'count', 0, count).onChange(function () {
-        stemMesh.count = api.count;
-        blossomMesh.count = api.count;
-    });
-
-    // gui.addColor( api, 'backgroundColor' ).onChange( function () {
-
-    // 	scene.background.setHex( api.backgroundColor );
-
-    // } );
-
-    // gui.addColor( api, 'surfaceColor' ).onChange( function () {
-
-    // 	surfaceMaterial.color.setHex( api.surfaceColor );
-
-    // } );
-
-    gui.add(api, 'distribution').options(['random', 'weighted']).onChange(resample);
-    gui.add(api, 'resample');
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function resample() {
-    const vertexCount = surface.geometry.getAttribute('position').count;
-
-    console.info('Sampling ' + count + ' points from a surface with ' + vertexCount + ' vertices...');
-
-    //
-
-    console.time('.build()');
-
-    sampler = new MeshSurfaceSampler(surface).setWeightAttribute(api.distribution === 'weighted' ? 'uv' : null).build();
-
-    console.timeEnd('.build()');
-
-    //
-
-    console.time('.sample()');
-
-    for (let i = 0; i < count; i++) {
-        ages[i] = Math.random();
-        scales[i] = scaleCurve(ages[i]);
-
-        resampleParticle(i);
-    }
-
-    console.timeEnd('.sample()');
-
-    stemMesh.instanceMatrix.needsUpdate = true;
-    blossomMesh.instanceMatrix.needsUpdate = true;
-}
-
-function resampleParticle(i) {
-    sampler.sample(_position, _normal);
-    _normal.add(_position);
-
-    dummy.position.copy(_position);
-    dummy.scale.set(scales[i], scales[i], scales[i]);
-    dummy.lookAt(_normal);
-    dummy.updateMatrix();
-
-    stemMesh.setMatrixAt(i, dummy.matrix);
-    blossomMesh.setMatrixAt(i, dummy.matrix);
-}
-
-function updateParticle(i) {
-    // Update lifecycle.
-
-    ages[i] += 0.005;
-
-    if (ages[i] >= 1) {
-        ages[i] = 0.001;
-        scales[i] = scaleCurve(ages[i]);
-
-        resampleParticle(i);
-
-        return;
-    }
-
-    // Update scale.
-
-    const prevScale = scales[i];
-    scales[i] = scaleCurve(ages[i]);
-    _scale.set(scales[i] / prevScale, scales[i] / prevScale, scales[i] / prevScale);
-
-    // Update transform.
-
-    stemMesh.getMatrixAt(i, dummy.matrix);
-    dummy.matrix.scale(_scale);
-    stemMesh.setMatrixAt(i, dummy.matrix);
-    blossomMesh.setMatrixAt(i, dummy.matrix);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    if (stemMesh && blossomMesh) {
-        const time = Date.now() * 0.001;
-
-        scene.rotation.x = Math.sin(time / 4);
-        scene.rotation.y = Math.sin(time / 2);
-
-        for (let i = 0; i < api.count; i++) {
-            updateParticle(i);
-        }
-
-        stemMesh.instanceMatrix.needsUpdate = true;
-        blossomMesh.instanceMatrix.needsUpdate = true;
-
-        stemMesh.computeBoundingSphere();
-        blossomMesh.computeBoundingSphere();
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_buffergeometry.ts b/examples-testing/examples/webgl_interactive_buffergeometry.ts
deleted file mode 100644
index 1d6608b13..000000000
--- a/examples-testing/examples/webgl_interactive_buffergeometry.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let raycaster, pointer;
-
-let mesh, line;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 3500);
-    camera.position.z = 2750;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-    scene.fog = new THREE.Fog(0x050505, 2000, 3500);
-
-    //
-
-    scene.add(new THREE.AmbientLight(0x444444, 3));
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 1.5);
-    light1.position.set(1, 1, 1);
-    scene.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 4.5);
-    light2.position.set(0, -1, 0);
-    scene.add(light2);
-
-    //
-
-    const triangles = 5000;
-
-    let geometry = new THREE.BufferGeometry();
-
-    const positions = new Float32Array(triangles * 3 * 3);
-    const normals = new Float32Array(triangles * 3 * 3);
-    const colors = new Float32Array(triangles * 3 * 3);
-
-    const color = new THREE.Color();
-
-    const n = 800,
-        n2 = n / 2; // triangles spread in the cube
-    const d = 120,
-        d2 = d / 2; // individual triangle size
-
-    const pA = new THREE.Vector3();
-    const pB = new THREE.Vector3();
-    const pC = new THREE.Vector3();
-
-    const cb = new THREE.Vector3();
-    const ab = new THREE.Vector3();
-
-    for (let i = 0; i < positions.length; i += 9) {
-        // positions
-
-        const x = Math.random() * n - n2;
-        const y = Math.random() * n - n2;
-        const z = Math.random() * n - n2;
-
-        const ax = x + Math.random() * d - d2;
-        const ay = y + Math.random() * d - d2;
-        const az = z + Math.random() * d - d2;
-
-        const bx = x + Math.random() * d - d2;
-        const by = y + Math.random() * d - d2;
-        const bz = z + Math.random() * d - d2;
-
-        const cx = x + Math.random() * d - d2;
-        const cy = y + Math.random() * d - d2;
-        const cz = z + Math.random() * d - d2;
-
-        positions[i] = ax;
-        positions[i + 1] = ay;
-        positions[i + 2] = az;
-
-        positions[i + 3] = bx;
-        positions[i + 4] = by;
-        positions[i + 5] = bz;
-
-        positions[i + 6] = cx;
-        positions[i + 7] = cy;
-        positions[i + 8] = cz;
-
-        // flat face normals
-
-        pA.set(ax, ay, az);
-        pB.set(bx, by, bz);
-        pC.set(cx, cy, cz);
-
-        cb.subVectors(pC, pB);
-        ab.subVectors(pA, pB);
-        cb.cross(ab);
-
-        cb.normalize();
-
-        const nx = cb.x;
-        const ny = cb.y;
-        const nz = cb.z;
-
-        normals[i] = nx;
-        normals[i + 1] = ny;
-        normals[i + 2] = nz;
-
-        normals[i + 3] = nx;
-        normals[i + 4] = ny;
-        normals[i + 5] = nz;
-
-        normals[i + 6] = nx;
-        normals[i + 7] = ny;
-        normals[i + 8] = nz;
-
-        // colors
-
-        const vx = x / n + 0.5;
-        const vy = y / n + 0.5;
-        const vz = z / n + 0.5;
-
-        color.setRGB(vx, vy, vz);
-
-        colors[i] = color.r;
-        colors[i + 1] = color.g;
-        colors[i + 2] = color.b;
-
-        colors[i + 3] = color.r;
-        colors[i + 4] = color.g;
-        colors[i + 5] = color.b;
-
-        colors[i + 6] = color.r;
-        colors[i + 7] = color.g;
-        colors[i + 8] = color.b;
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
-    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
-
-    geometry.computeBoundingSphere();
-
-    let material = new THREE.MeshPhongMaterial({
-        color: 0xaaaaaa,
-        specular: 0xffffff,
-        shininess: 250,
-        side: THREE.DoubleSide,
-        vertexColors: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    raycaster = new THREE.Raycaster();
-
-    pointer = new THREE.Vector2();
-
-    geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(4 * 3), 3));
-
-    material = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true });
-
-    line = new THREE.Line(geometry, material);
-    scene.add(line);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('pointermove', onPointerMove);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.001;
-
-    mesh.rotation.x = time * 0.15;
-    mesh.rotation.y = time * 0.25;
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObject(mesh);
-
-    if (intersects.length > 0) {
-        const intersect = intersects[0];
-        const face = intersect.face;
-
-        const linePosition = line.geometry.attributes.position;
-        const meshPosition = mesh.geometry.attributes.position;
-
-        linePosition.copyAt(0, meshPosition, face.a);
-        linePosition.copyAt(1, meshPosition, face.b);
-        linePosition.copyAt(2, meshPosition, face.c);
-        linePosition.copyAt(3, meshPosition, face.a);
-
-        mesh.updateMatrix();
-
-        line.geometry.applyMatrix4(mesh.matrix);
-
-        line.visible = true;
-    } else {
-        line.visible = false;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_cubes.ts b/examples-testing/examples/webgl_interactive_cubes.ts
deleted file mode 100644
index adfcfddf8..000000000
--- a/examples-testing/examples/webgl_interactive_cubes.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let stats;
-let camera, scene, raycaster, renderer;
-
-let INTERSECTED;
-let theta = 0;
-
-const pointer = new THREE.Vector2();
-const radius = 5;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1).normalize();
-    scene.add(light);
-
-    const geometry = new THREE.BoxGeometry();
-
-    for (let i = 0; i < 2000; i++) {
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
-
-        object.position.x = Math.random() * 40 - 20;
-        object.position.y = Math.random() * 40 - 20;
-        object.position.z = Math.random() * 40 - 20;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        object.scale.z = Math.random() + 0.5;
-
-        scene.add(object);
-    }
-
-    raycaster = new THREE.Raycaster();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    theta += 0.1;
-
-    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
-    camera.lookAt(scene.position);
-
-    camera.updateMatrixWorld();
-
-    // find intersections
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObjects(scene.children, false);
-
-    if (intersects.length > 0) {
-        if (INTERSECTED != intersects[0].object) {
-            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
-
-            INTERSECTED = intersects[0].object;
-            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
-            INTERSECTED.material.emissive.setHex(0xff0000);
-        }
-    } else {
-        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
-
-        INTERSECTED = null;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_cubes_gpu.ts b/examples-testing/examples/webgl_interactive_cubes_gpu.ts
deleted file mode 100644
index 2644469c3..000000000
--- a/examples-testing/examples/webgl_interactive_cubes_gpu.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let container, stats;
-let camera, controls, scene, renderer;
-let pickingTexture, pickingScene;
-let highlightBox;
-
-const pickingData = [];
-
-const pointer = new THREE.Vector2();
-const offset = new THREE.Vector3(10, 10, 10);
-const clearColor = new THREE.Color();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 500, 2000);
-    scene.add(light);
-
-    const defaultMaterial = new THREE.MeshPhongMaterial({
-        color: 0xffffff,
-        flatShading: true,
-        vertexColors: true,
-        shininess: 0,
-    });
-
-    // set up the picking texture to use a 32 bit integer so we can write and read integer ids from it
-    pickingScene = new THREE.Scene();
-    pickingTexture = new THREE.WebGLRenderTarget(1, 1, {
-        type: THREE.IntType,
-        format: THREE.RGBAIntegerFormat,
-        internalFormat: 'RGBA32I',
-    });
-    const pickingMaterial = new THREE.ShaderMaterial({
-        glslVersion: THREE.GLSL3,
-
-        vertexShader: /* glsl */ `
-						attribute int id;
-						flat varying int vid;
-						void main() {
-
-							vid = id;
-							gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
-						}
-					`,
-
-        fragmentShader: /* glsl */ `
-						layout(location = 0) out int out_id;
-						flat varying int vid;
-
-						void main() {
-
-							out_id = vid;
-
-						}
-					`,
-    });
-
-    function applyId(geometry, id) {
-        const position = geometry.attributes.position;
-        const array = new Int16Array(position.count);
-        array.fill(id);
-
-        const bufferAttribute = new THREE.Int16BufferAttribute(array, 1, false);
-        bufferAttribute.gpuType = THREE.IntType;
-        geometry.setAttribute('id', bufferAttribute);
-    }
-
-    function applyVertexColors(geometry, color) {
-        const position = geometry.attributes.position;
-        const colors = [];
-
-        for (let i = 0; i < position.count; i++) {
-            colors.push(color.r, color.g, color.b);
-        }
-
-        geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-    }
-
-    const geometries = [];
-    const matrix = new THREE.Matrix4();
-    const quaternion = new THREE.Quaternion();
-    const color = new THREE.Color();
-
-    for (let i = 0; i < 5000; i++) {
-        const geometry = new THREE.BoxGeometry();
-
-        const position = new THREE.Vector3();
-        position.x = Math.random() * 10000 - 5000;
-        position.y = Math.random() * 6000 - 3000;
-        position.z = Math.random() * 8000 - 4000;
-
-        const rotation = new THREE.Euler();
-        rotation.x = Math.random() * 2 * Math.PI;
-        rotation.y = Math.random() * 2 * Math.PI;
-        rotation.z = Math.random() * 2 * Math.PI;
-
-        const scale = new THREE.Vector3();
-        scale.x = Math.random() * 200 + 100;
-        scale.y = Math.random() * 200 + 100;
-        scale.z = Math.random() * 200 + 100;
-
-        quaternion.setFromEuler(rotation);
-        matrix.compose(position, quaternion, scale);
-
-        geometry.applyMatrix4(matrix);
-
-        // give the geometry's vertices a random color to be displayed and an integer
-        // identifier as a vertex attribute so boxes can be identified after being merged.
-        applyVertexColors(geometry, color.setHex(Math.random() * 0xffffff));
-        applyId(geometry, i);
-
-        geometries.push(geometry);
-
-        pickingData[i] = {
-            position: position,
-            rotation: rotation,
-            scale: scale,
-        };
-    }
-
-    const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
-    scene.add(new THREE.Mesh(mergedGeometry, defaultMaterial));
-    pickingScene.add(new THREE.Mesh(mergedGeometry, pickingMaterial));
-
-    highlightBox = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshLambertMaterial({ color: 0xffff00 }));
-    scene.add(highlightBox);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.rotateSpeed = 1.0;
-    controls.zoomSpeed = 1.2;
-    controls.panSpeed = 0.8;
-    controls.noZoom = false;
-    controls.noPan = false;
-    controls.staticMoving = true;
-    controls.dynamicDampingFactor = 0.3;
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    renderer.domElement.addEventListener('pointermove', onPointerMove);
-}
-
-//
-
-function onPointerMove(e) {
-    pointer.x = e.clientX;
-    pointer.y = e.clientY;
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function pick() {
-    // render the picking scene off-screen
-    // set the view offset to represent just a single pixel under the mouse
-    const dpr = window.devicePixelRatio;
-    camera.setViewOffset(
-        renderer.domElement.width,
-        renderer.domElement.height,
-        Math.floor(pointer.x * dpr),
-        Math.floor(pointer.y * dpr),
-        1,
-        1,
-    );
-
-    // render the scene
-    renderer.setRenderTarget(pickingTexture);
-
-    // clear the background to - 1 meaning no item was hit
-    clearColor.setRGB(-1, -1, -1);
-    renderer.setClearColor(clearColor);
-    renderer.render(pickingScene, camera);
-
-    // clear the view offset so rendering returns to normal
-    camera.clearViewOffset();
-
-    // create buffer for reading single pixel
-    const pixelBuffer = new Int32Array(4);
-
-    // read the pixel
-    renderer.readRenderTargetPixelsAsync(pickingTexture, 0, 0, 1, 1, pixelBuffer).then(() => {
-        const id = pixelBuffer[0];
-        if (id !== -1) {
-            // move our highlightBox so that it surrounds the picked object
-            const data = pickingData[id];
-            highlightBox.position.copy(data.position);
-            highlightBox.rotation.copy(data.rotation);
-            highlightBox.scale.copy(data.scale).add(offset);
-            highlightBox.visible = true;
-        } else {
-            highlightBox.visible = false;
-        }
-    });
-}
-
-function render() {
-    controls.update();
-
-    pick();
-
-    renderer.setRenderTarget(null);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_cubes_ortho.ts b/examples-testing/examples/webgl_interactive_cubes_ortho.ts
deleted file mode 100644
index 520674b5f..000000000
--- a/examples-testing/examples/webgl_interactive_cubes_ortho.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let stats;
-let camera, scene, raycaster, renderer;
-
-let theta = 0;
-let INTERSECTED;
-
-const pointer = new THREE.Vector2();
-const radius = 25;
-const frustumSize = 50;
-
-init();
-
-function init() {
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera(
-        (frustumSize * aspect) / -2,
-        (frustumSize * aspect) / 2,
-        frustumSize / 2,
-        frustumSize / -2,
-        0.1,
-        100,
-    );
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1).normalize();
-    scene.add(light);
-
-    const geometry = new THREE.BoxGeometry();
-
-    for (let i = 0; i < 2000; i++) {
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
-
-        object.position.x = Math.random() * 40 - 20;
-        object.position.y = Math.random() * 40 - 20;
-        object.position.z = Math.random() * 40 - 20;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        object.scale.z = Math.random() + 0.5;
-
-        scene.add(object);
-    }
-
-    raycaster = new THREE.Raycaster();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    document.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera.left = (-frustumSize * aspect) / 2;
-    camera.right = (frustumSize * aspect) / 2;
-    camera.top = frustumSize / 2;
-    camera.bottom = -frustumSize / 2;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    theta += 0.1;
-
-    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
-    camera.lookAt(scene.position);
-
-    camera.updateMatrixWorld();
-
-    // find intersections
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObjects(scene.children, false);
-
-    if (intersects.length > 0) {
-        if (INTERSECTED != intersects[0].object) {
-            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
-
-            INTERSECTED = intersects[0].object;
-            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
-            INTERSECTED.material.emissive.setHex(0xff0000);
-        }
-    } else {
-        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
-
-        INTERSECTED = null;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_lines.ts b/examples-testing/examples/webgl_interactive_lines.ts
deleted file mode 100644
index b137c5501..000000000
--- a/examples-testing/examples/webgl_interactive_lines.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-let camera, scene, raycaster, renderer, parentTransform, sphereInter;
-
-const pointer = new THREE.Vector2();
-const radius = 100;
-let theta = 0;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    const info = document.createElement('div');
-    info.style.position = 'absolute';
-    info.style.top = '10px';
-    info.style.width = '100%';
-    info.style.textAlign = 'center';
-    info.innerHTML =
-        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - interactive lines';
-    container.appendChild(info);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const geometry = new THREE.SphereGeometry(5);
-    const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
-
-    sphereInter = new THREE.Mesh(geometry, material);
-    sphereInter.visible = false;
-    scene.add(sphereInter);
-
-    const lineGeometry = new THREE.BufferGeometry();
-    const points = [];
-
-    const point = new THREE.Vector3();
-    const direction = new THREE.Vector3();
-
-    for (let i = 0; i < 50; i++) {
-        direction.x += Math.random() - 0.5;
-        direction.y += Math.random() - 0.5;
-        direction.z += Math.random() - 0.5;
-        direction.normalize().multiplyScalar(10);
-
-        point.add(direction);
-        points.push(point.x, point.y, point.z);
-    }
-
-    lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
-
-    parentTransform = new THREE.Object3D();
-    parentTransform.position.x = Math.random() * 40 - 20;
-    parentTransform.position.y = Math.random() * 40 - 20;
-    parentTransform.position.z = Math.random() * 40 - 20;
-
-    parentTransform.rotation.x = Math.random() * 2 * Math.PI;
-    parentTransform.rotation.y = Math.random() * 2 * Math.PI;
-    parentTransform.rotation.z = Math.random() * 2 * Math.PI;
-
-    parentTransform.scale.x = Math.random() + 0.5;
-    parentTransform.scale.y = Math.random() + 0.5;
-    parentTransform.scale.z = Math.random() + 0.5;
-
-    for (let i = 0; i < 50; i++) {
-        let object;
-
-        const lineMaterial = new THREE.LineBasicMaterial({ color: Math.random() * 0xffffff });
-
-        if (Math.random() > 0.5) {
-            object = new THREE.Line(lineGeometry, lineMaterial);
-        } else {
-            object = new THREE.LineSegments(lineGeometry, lineMaterial);
-        }
-
-        object.position.x = Math.random() * 400 - 200;
-        object.position.y = Math.random() * 400 - 200;
-        object.position.z = Math.random() * 400 - 200;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        object.scale.z = Math.random() + 0.5;
-
-        parentTransform.add(object);
-    }
-
-    scene.add(parentTransform);
-
-    raycaster = new THREE.Raycaster();
-    raycaster.params.Line.threshold = 3;
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    theta += 0.1;
-
-    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
-    camera.lookAt(scene.position);
-
-    camera.updateMatrixWorld();
-
-    // find intersections
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObjects(parentTransform.children, true);
-
-    if (intersects.length > 0) {
-        sphereInter.visible = true;
-        sphereInter.position.copy(intersects[0].point);
-    } else {
-        sphereInter.visible = false;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_points.ts b/examples-testing/examples/webgl_interactive_points.ts
deleted file mode 100644
index 93113b867..000000000
--- a/examples-testing/examples/webgl_interactive_points.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-let renderer, scene, camera, stats;
-
-let particles;
-
-const PARTICLE_SIZE = 20;
-
-let raycaster, intersects;
-let pointer, INTERSECTED;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 250;
-
-    //
-
-    let boxGeometry = new THREE.BoxGeometry(200, 200, 200, 16, 16, 16);
-
-    // if normal and uv attributes are not removed, mergeVertices() can't consolidate indentical vertices with different normal/uv data
-
-    boxGeometry.deleteAttribute('normal');
-    boxGeometry.deleteAttribute('uv');
-
-    boxGeometry = BufferGeometryUtils.mergeVertices(boxGeometry);
-
-    //
-
-    const positionAttribute = boxGeometry.getAttribute('position');
-
-    const colors = [];
-    const sizes = [];
-
-    const color = new THREE.Color();
-
-    for (let i = 0, l = positionAttribute.count; i < l; i++) {
-        color.setHSL(0.01 + 0.1 * (i / l), 1.0, 0.5);
-        color.toArray(colors, i * 3);
-
-        sizes[i] = PARTICLE_SIZE * 0.5;
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', positionAttribute);
-    geometry.setAttribute('customColor', new THREE.Float32BufferAttribute(colors, 3));
-    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
-
-    //
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            color: { value: new THREE.Color(0xffffff) },
-            pointTexture: { value: new THREE.TextureLoader().load('textures/sprites/disc.png') },
-            alphaTest: { value: 0.9 },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    //
-
-    particles = new THREE.Points(geometry, material);
-    scene.add(particles);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    raycaster = new THREE.Raycaster();
-    pointer = new THREE.Vector2();
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('pointermove', onPointerMove);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    particles.rotation.x += 0.0005;
-    particles.rotation.y += 0.001;
-
-    const geometry = particles.geometry;
-    const attributes = geometry.attributes;
-
-    raycaster.setFromCamera(pointer, camera);
-
-    intersects = raycaster.intersectObject(particles);
-
-    if (intersects.length > 0) {
-        if (INTERSECTED != intersects[0].index) {
-            attributes.size.array[INTERSECTED] = PARTICLE_SIZE;
-
-            INTERSECTED = intersects[0].index;
-
-            attributes.size.array[INTERSECTED] = PARTICLE_SIZE * 1.25;
-            attributes.size.needsUpdate = true;
-        }
-    } else if (INTERSECTED !== null) {
-        attributes.size.array[INTERSECTED] = PARTICLE_SIZE;
-        attributes.size.needsUpdate = true;
-        INTERSECTED = null;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_raycasting_points.ts b/examples-testing/examples/webgl_interactive_raycasting_points.ts
deleted file mode 100644
index 41c158a43..000000000
--- a/examples-testing/examples/webgl_interactive_raycasting_points.ts
+++ /dev/null
@@ -1,220 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let renderer, scene, camera, stats;
-let pointclouds;
-let raycaster;
-let intersection = null;
-let spheresIndex = 0;
-let clock;
-let toggle = 0;
-
-const pointer = new THREE.Vector2();
-const spheres = [];
-
-const threshold = 0.1;
-const pointSize = 0.05;
-const width = 80;
-const length = 160;
-const rotateY = new THREE.Matrix4().makeRotationY(0.005);
-
-init();
-
-function generatePointCloudGeometry(color, width, length) {
-    const geometry = new THREE.BufferGeometry();
-    const numPoints = width * length;
-
-    const positions = new Float32Array(numPoints * 3);
-    const colors = new Float32Array(numPoints * 3);
-
-    let k = 0;
-
-    for (let i = 0; i < width; i++) {
-        for (let j = 0; j < length; j++) {
-            const u = i / width;
-            const v = j / length;
-            const x = u - 0.5;
-            const y = (Math.cos(u * Math.PI * 4) + Math.sin(v * Math.PI * 8)) / 20;
-            const z = v - 0.5;
-
-            positions[3 * k] = x;
-            positions[3 * k + 1] = y;
-            positions[3 * k + 2] = z;
-
-            const intensity = (y + 0.1) * 5;
-            colors[3 * k] = color.r * intensity;
-            colors[3 * k + 1] = color.g * intensity;
-            colors[3 * k + 2] = color.b * intensity;
-
-            k++;
-        }
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
-    geometry.computeBoundingBox();
-
-    return geometry;
-}
-
-function generatePointcloud(color, width, length) {
-    const geometry = generatePointCloudGeometry(color, width, length);
-    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
-
-    return new THREE.Points(geometry, material);
-}
-
-function generateIndexedPointcloud(color, width, length) {
-    const geometry = generatePointCloudGeometry(color, width, length);
-    const numPoints = width * length;
-    const indices = new Uint16Array(numPoints);
-
-    let k = 0;
-
-    for (let i = 0; i < width; i++) {
-        for (let j = 0; j < length; j++) {
-            indices[k] = k;
-            k++;
-        }
-    }
-
-    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
-
-    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
-
-    return new THREE.Points(geometry, material);
-}
-
-function generateIndexedWithOffsetPointcloud(color, width, length) {
-    const geometry = generatePointCloudGeometry(color, width, length);
-    const numPoints = width * length;
-    const indices = new Uint16Array(numPoints);
-
-    let k = 0;
-
-    for (let i = 0; i < width; i++) {
-        for (let j = 0; j < length; j++) {
-            indices[k] = k;
-            k++;
-        }
-    }
-
-    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
-    geometry.addGroup(0, indices.length);
-
-    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true });
-
-    return new THREE.Points(geometry, material);
-}
-
-function init() {
-    const container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(10, 10, 10);
-    camera.lookAt(scene.position);
-    camera.updateMatrix();
-
-    //
-
-    const pcBuffer = generatePointcloud(new THREE.Color(1, 0, 0), width, length);
-    pcBuffer.scale.set(5, 10, 10);
-    pcBuffer.position.set(-5, 0, 0);
-    scene.add(pcBuffer);
-
-    const pcIndexed = generateIndexedPointcloud(new THREE.Color(0, 1, 0), width, length);
-    pcIndexed.scale.set(5, 10, 10);
-    pcIndexed.position.set(0, 0, 0);
-    scene.add(pcIndexed);
-
-    const pcIndexedOffset = generateIndexedWithOffsetPointcloud(new THREE.Color(0, 1, 1), width, length);
-    pcIndexedOffset.scale.set(5, 10, 10);
-    pcIndexedOffset.position.set(5, 0, 0);
-    scene.add(pcIndexedOffset);
-
-    pointclouds = [pcBuffer, pcIndexed, pcIndexedOffset];
-
-    //
-
-    const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
-    const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
-
-    for (let i = 0; i < 40; i++) {
-        const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
-        scene.add(sphere);
-        spheres.push(sphere);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    raycaster = new THREE.Raycaster();
-    raycaster.params.Points.threshold = threshold;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('pointermove', onPointerMove);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    camera.applyMatrix4(rotateY);
-    camera.updateMatrixWorld();
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersections = raycaster.intersectObjects(pointclouds, false);
-    intersection = intersections.length > 0 ? intersections[0] : null;
-
-    if (toggle > 0.02 && intersection !== null) {
-        spheres[spheresIndex].position.copy(intersection.point);
-        spheres[spheresIndex].scale.set(1, 1, 1);
-        spheresIndex = (spheresIndex + 1) % spheres.length;
-
-        toggle = 0;
-    }
-
-    for (let i = 0; i < spheres.length; i++) {
-        const sphere = spheres[i];
-        sphere.scale.multiplyScalar(0.98);
-        sphere.scale.clampScalar(0.01, 1);
-    }
-
-    toggle += clock.getDelta();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_interactive_voxelpainter.ts b/examples-testing/examples/webgl_interactive_voxelpainter.ts
deleted file mode 100644
index 48b16f3b7..000000000
--- a/examples-testing/examples/webgl_interactive_voxelpainter.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let plane;
-let pointer,
-    raycaster,
-    isShiftDown = false;
-
-let rollOverMesh, rollOverMaterial;
-let cubeGeo, cubeMaterial;
-
-const objects = [];
-
-init();
-render();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(500, 800, 1300);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    // roll-over helpers
-
-    const rollOverGeo = new THREE.BoxGeometry(50, 50, 50);
-    rollOverMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true });
-    rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial);
-    scene.add(rollOverMesh);
-
-    // cubes
-
-    const map = new THREE.TextureLoader().load('textures/square-outline-textured.png');
-    map.colorSpace = THREE.SRGBColorSpace;
-    cubeGeo = new THREE.BoxGeometry(50, 50, 50);
-    cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xfeb74c, map: map });
-
-    // grid
-
-    const gridHelper = new THREE.GridHelper(1000, 20);
-    scene.add(gridHelper);
-
-    //
-
-    raycaster = new THREE.Raycaster();
-    pointer = new THREE.Vector2();
-
-    const geometry = new THREE.PlaneGeometry(1000, 1000);
-    geometry.rotateX(-Math.PI / 2);
-
-    plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ visible: false }));
-    scene.add(plane);
-
-    objects.push(plane);
-
-    // lights
-
-    const ambientLight = new THREE.AmbientLight(0x606060, 3);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(1, 0.75, 0.5).normalize();
-    scene.add(directionalLight);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerdown', onPointerDown);
-    document.addEventListener('keydown', onDocumentKeyDown);
-    document.addEventListener('keyup', onDocumentKeyUp);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function onPointerMove(event) {
-    pointer.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObjects(objects, false);
-
-    if (intersects.length > 0) {
-        const intersect = intersects[0];
-
-        rollOverMesh.position.copy(intersect.point).add(intersect.face.normal);
-        rollOverMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
-
-        render();
-    }
-}
-
-function onPointerDown(event) {
-    pointer.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObjects(objects, false);
-
-    if (intersects.length > 0) {
-        const intersect = intersects[0];
-
-        // delete cube
-
-        if (isShiftDown) {
-            if (intersect.object !== plane) {
-                scene.remove(intersect.object);
-
-                objects.splice(objects.indexOf(intersect.object), 1);
-            }
-
-            // create cube
-        } else {
-            const voxel = new THREE.Mesh(cubeGeo, cubeMaterial);
-            voxel.position.copy(intersect.point).add(intersect.face.normal);
-            voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
-            scene.add(voxel);
-
-            objects.push(voxel);
-        }
-
-        render();
-    }
-}
-
-function onDocumentKeyDown(event) {
-    switch (event.keyCode) {
-        case 16:
-            isShiftDown = true;
-            break;
-    }
-}
-
-function onDocumentKeyUp(event) {
-    switch (event.keyCode) {
-        case 16:
-            isShiftDown = false;
-            break;
-    }
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_layers.ts b/examples-testing/examples/webgl_layers.ts
deleted file mode 100644
index 8bdcda7f9..000000000
--- a/examples-testing/examples/webgl_layers.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let container, stats;
-let camera, scene, renderer;
-
-let theta = 0;
-const radius = 5;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.layers.enable(0); // enabled by default
-    camera.layers.enable(1);
-    camera.layers.enable(2);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    const light = new THREE.PointLight(0xffffff, 3, 0, 0);
-    light.layers.enable(0);
-    light.layers.enable(1);
-    light.layers.enable(2);
-
-    scene.add(camera);
-    camera.add(light);
-
-    const colors = [0xff0000, 0x00ff00, 0x0000ff];
-    const geometry = new THREE.BoxGeometry();
-
-    for (let i = 0; i < 300; i++) {
-        const layer = i % 3;
-
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: colors[layer] }));
-
-        object.position.x = Math.random() * 40 - 20;
-        object.position.y = Math.random() * 40 - 20;
-        object.position.z = Math.random() * 40 - 20;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        object.scale.z = Math.random() + 0.5;
-
-        object.layers.set(layer);
-
-        scene.add(object);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const layers = {
-        'toggle red': function () {
-            camera.layers.toggle(0);
-        },
-
-        'toggle green': function () {
-            camera.layers.toggle(1);
-        },
-
-        'toggle blue': function () {
-            camera.layers.toggle(2);
-        },
-
-        'enable all': function () {
-            camera.layers.enableAll();
-        },
-
-        'disable all': function () {
-            camera.layers.disableAll();
-        },
-    };
-
-    //
-    // Init gui
-    const gui = new GUI();
-    gui.add(layers, 'toggle red');
-    gui.add(layers, 'toggle green');
-    gui.add(layers, 'toggle blue');
-    gui.add(layers, 'enable all');
-    gui.add(layers, 'disable all');
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    theta += 0.1;
-
-    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
-    camera.lookAt(scene.position);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lensflares.ts b/examples-testing/examples/webgl_lensflares.ts
deleted file mode 100644
index 230cebfa0..000000000
--- a/examples-testing/examples/webgl_lensflares.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FlyControls } from 'three/addons/controls/FlyControls.js';
-import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-let controls;
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 15000);
-    camera.position.z = 250;
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color().setHSL(0.51, 0.4, 0.01, THREE.SRGBColorSpace);
-    scene.fog = new THREE.Fog(scene.background, 3500, 15000);
-
-    // world
-
-    const s = 250;
-
-    const geometry = new THREE.BoxGeometry(s, s, s);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0xffffff, shininess: 50 });
-
-    for (let i = 0; i < 3000; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = 8000 * (2.0 * Math.random() - 1.0);
-        mesh.position.y = 8000 * (2.0 * Math.random() - 1.0);
-        mesh.position.z = 8000 * (2.0 * Math.random() - 1.0);
-
-        mesh.rotation.x = Math.random() * Math.PI;
-        mesh.rotation.y = Math.random() * Math.PI;
-        mesh.rotation.z = Math.random() * Math.PI;
-
-        mesh.matrixAutoUpdate = false;
-        mesh.updateMatrix();
-
-        scene.add(mesh);
-    }
-
-    // lights
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 0.15);
-    dirLight.position.set(0, -1, 0).normalize();
-    dirLight.color.setHSL(0.1, 0.7, 0.5);
-    scene.add(dirLight);
-
-    // lensflares
-    const textureLoader = new THREE.TextureLoader();
-
-    const textureFlare0 = textureLoader.load('textures/lensflare/lensflare0.png');
-    const textureFlare3 = textureLoader.load('textures/lensflare/lensflare3.png');
-
-    addLight(0.55, 0.9, 0.5, 5000, 0, -1000);
-    addLight(0.08, 0.8, 0.5, 0, 0, -1000);
-    addLight(0.995, 0.5, 0.9, 5000, 5000, -1000);
-
-    function addLight(h, s, l, x, y, z) {
-        const light = new THREE.PointLight(0xffffff, 1.5, 2000, 0);
-        light.color.setHSL(h, s, l);
-        light.position.set(x, y, z);
-        scene.add(light);
-
-        const lensflare = new Lensflare();
-        lensflare.addElement(new LensflareElement(textureFlare0, 700, 0, light.color));
-        lensflare.addElement(new LensflareElement(textureFlare3, 60, 0.6));
-        lensflare.addElement(new LensflareElement(textureFlare3, 70, 0.7));
-        lensflare.addElement(new LensflareElement(textureFlare3, 120, 0.9));
-        lensflare.addElement(new LensflareElement(textureFlare3, 70, 1));
-        light.add(lensflare);
-    }
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    controls = new FlyControls(camera, renderer.domElement);
-
-    controls.movementSpeed = 2500;
-    controls.domElement = container;
-    controls.rollSpeed = Math.PI / 6;
-    controls.autoForward = false;
-    controls.dragToLook = false;
-
-    // stats
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // events
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    controls.update(delta);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lightprobe.ts b/examples-testing/examples/webgl_lightprobe.ts
deleted file mode 100644
index 2efcad52a..000000000
--- a/examples-testing/examples/webgl_lightprobe.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
-
-let mesh, renderer, scene, camera;
-
-let gui;
-
-let lightProbe;
-let directionalLight;
-
-// linear color space
-const API = {
-    lightProbeIntensity: 1.0,
-    directionalLightIntensity: 0.6,
-    envMapIntensity: 1,
-};
-
-init();
-
-function init() {
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    // tone mapping
-    renderer.toneMapping = THREE.NoToneMapping;
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 30);
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 10;
-    controls.maxDistance = 50;
-    controls.enablePan = false;
-
-    // probe
-    lightProbe = new THREE.LightProbe();
-    scene.add(lightProbe);
-
-    // light
-    directionalLight = new THREE.DirectionalLight(0xffffff, API.directionalLightIntensity);
-    directionalLight.position.set(10, 10, 10);
-    scene.add(directionalLight);
-
-    // envmap
-    const genCubeUrls = function (prefix, postfix) {
-        return [
-            prefix + 'px' + postfix,
-            prefix + 'nx' + postfix,
-            prefix + 'py' + postfix,
-            prefix + 'ny' + postfix,
-            prefix + 'pz' + postfix,
-            prefix + 'nz' + postfix,
-        ];
-    };
-
-    const urls = genCubeUrls('textures/cube/pisa/', '.png');
-
-    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
-        scene.background = cubeTexture;
-
-        lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture));
-
-        const geometry = new THREE.SphereGeometry(5, 64, 32);
-        //const geometry = new THREE.TorusKnotGeometry( 4, 1.5, 256, 32, 2, 3 );
-
-        const material = new THREE.MeshStandardMaterial({
-            color: 0xffffff,
-            metalness: 0,
-            roughness: 0,
-            envMap: cubeTexture,
-            envMapIntensity: API.envMapIntensity,
-        });
-
-        // mesh
-        mesh = new THREE.Mesh(geometry, material);
-        scene.add(mesh);
-
-        render();
-    });
-
-    // gui
-    gui = new GUI({ title: 'Intensity' });
-
-    gui.add(API, 'lightProbeIntensity', 0, 1, 0.02)
-        .name('light probe')
-        .onChange(function () {
-            lightProbe.intensity = API.lightProbeIntensity;
-            render();
-        });
-
-    gui.add(API, 'directionalLightIntensity', 0, 1, 0.02)
-        .name('directional light')
-        .onChange(function () {
-            directionalLight.intensity = API.directionalLightIntensity;
-            render();
-        });
-
-    gui.add(API, 'envMapIntensity', 0, 1, 0.02)
-        .name('envMap')
-        .onChange(function () {
-            mesh.material.envMapIntensity = API.envMapIntensity;
-            render();
-        });
-
-    // listener
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lightprobe_cubecamera.ts b/examples-testing/examples/webgl_lightprobe_cubecamera.ts
deleted file mode 100644
index c714d2978..000000000
--- a/examples-testing/examples/webgl_lightprobe_cubecamera.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LightProbeHelper } from 'three/addons/helpers/LightProbeHelper.js';
-import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
-
-let renderer, scene, camera, cubeCamera;
-
-let lightProbe;
-
-init();
-
-function init() {
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 30);
-
-    const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
-
-    cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget);
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 10;
-    controls.maxDistance = 50;
-    controls.enablePan = false;
-
-    // probe
-    lightProbe = new THREE.LightProbe();
-    scene.add(lightProbe);
-
-    // envmap
-    const genCubeUrls = function (prefix, postfix) {
-        return [
-            prefix + 'px' + postfix,
-            prefix + 'nx' + postfix,
-            prefix + 'py' + postfix,
-            prefix + 'ny' + postfix,
-            prefix + 'pz' + postfix,
-            prefix + 'nz' + postfix,
-        ];
-    };
-
-    const urls = genCubeUrls('textures/cube/pisa/', '.png');
-
-    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
-        scene.background = cubeTexture;
-
-        cubeCamera.update(renderer, scene);
-
-        lightProbe.copy(LightProbeGenerator.fromCubeRenderTarget(renderer, cubeRenderTarget));
-
-        scene.add(new LightProbeHelper(lightProbe, 5));
-
-        render();
-    });
-
-    // listener
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lights_hemisphere.ts b/examples-testing/examples/webgl_lights_hemisphere.ts
deleted file mode 100644
index 15bc76099..000000000
--- a/examples-testing/examples/webgl_lights_hemisphere.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer;
-const mixers = [];
-let stats;
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.set(0, 0, 250);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color().setHSL(0.6, 0, 1);
-    scene.fog = new THREE.Fog(scene.background, 1, 5000);
-
-    // LIGHTS
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 2);
-    hemiLight.color.setHSL(0.6, 1, 0.6);
-    hemiLight.groundColor.setHSL(0.095, 1, 0.75);
-    hemiLight.position.set(0, 50, 0);
-    scene.add(hemiLight);
-
-    const hemiLightHelper = new THREE.HemisphereLightHelper(hemiLight, 10);
-    scene.add(hemiLightHelper);
-
-    //
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.color.setHSL(0.1, 1, 0.95);
-    dirLight.position.set(-1, 1.75, 1);
-    dirLight.position.multiplyScalar(30);
-    scene.add(dirLight);
-
-    dirLight.castShadow = true;
-
-    dirLight.shadow.mapSize.width = 2048;
-    dirLight.shadow.mapSize.height = 2048;
-
-    const d = 50;
-
-    dirLight.shadow.camera.left = -d;
-    dirLight.shadow.camera.right = d;
-    dirLight.shadow.camera.top = d;
-    dirLight.shadow.camera.bottom = -d;
-
-    dirLight.shadow.camera.far = 3500;
-    dirLight.shadow.bias = -0.0001;
-
-    const dirLightHelper = new THREE.DirectionalLightHelper(dirLight, 10);
-    scene.add(dirLightHelper);
-
-    // GROUND
-
-    const groundGeo = new THREE.PlaneGeometry(10000, 10000);
-    const groundMat = new THREE.MeshLambertMaterial({ color: 0xffffff });
-    groundMat.color.setHSL(0.095, 1, 0.75);
-
-    const ground = new THREE.Mesh(groundGeo, groundMat);
-    ground.position.y = -33;
-    ground.rotation.x = -Math.PI / 2;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // SKYDOME
-
-    const vertexShader = document.getElementById('vertexShader').textContent;
-    const fragmentShader = document.getElementById('fragmentShader').textContent;
-    const uniforms = {
-        topColor: { value: new THREE.Color(0x0077ff) },
-        bottomColor: { value: new THREE.Color(0xffffff) },
-        offset: { value: 33 },
-        exponent: { value: 0.6 },
-    };
-    uniforms['topColor'].value.copy(hemiLight.color);
-
-    scene.fog.color.copy(uniforms['bottomColor'].value);
-
-    const skyGeo = new THREE.SphereGeometry(4000, 32, 15);
-    const skyMat = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: vertexShader,
-        fragmentShader: fragmentShader,
-        side: THREE.BackSide,
-    });
-
-    const sky = new THREE.Mesh(skyGeo, skyMat);
-    scene.add(sky);
-
-    // MODEL
-
-    const loader = new GLTFLoader();
-
-    loader.load('models/gltf/Flamingo.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-
-        const s = 0.35;
-        mesh.scale.set(s, s, s);
-        mesh.position.y = 15;
-        mesh.rotation.y = -1;
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-
-        const mixer = new THREE.AnimationMixer(mesh);
-        mixer.clipAction(gltf.animations[0]).setDuration(1).play();
-        mixers.push(mixer);
-    });
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-    renderer.shadowMap.enabled = true;
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    const params = {
-        toggleHemisphereLight: function () {
-            hemiLight.visible = !hemiLight.visible;
-            hemiLightHelper.visible = !hemiLightHelper.visible;
-        },
-        toggleDirectionalLight: function () {
-            dirLight.visible = !dirLight.visible;
-            dirLightHelper.visible = !dirLightHelper.visible;
-        },
-        shadowIntensity: 1,
-    };
-
-    const gui = new GUI();
-
-    gui.add(params, 'toggleHemisphereLight').name('toggle hemisphere light');
-    gui.add(params, 'toggleDirectionalLight').name('toggle directional light');
-    gui.add(params, 'shadowIntensity', 0, 1)
-        .name('shadow intensity')
-        .onChange(value => {
-            dirLight.shadow.intensity = value;
-        });
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    for (let i = 0; i < mixers.length; i++) {
-        mixers[i].update(delta);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lights_physical.ts b/examples-testing/examples/webgl_lights_physical.ts
deleted file mode 100644
index 707ef200e..000000000
--- a/examples-testing/examples/webgl_lights_physical.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, bulbLight, bulbMat, hemiLight, stats;
-let ballMat, cubeMat, floorMat;
-
-let previousShadowMap = false;
-
-// ref for lumens: http://www.power-sure.com/lumens.htm
-const bulbLuminousPowers = {
-    '110000 lm (1000W)': 110000,
-    '3500 lm (300W)': 3500,
-    '1700 lm (100W)': 1700,
-    '800 lm (60W)': 800,
-    '400 lm (40W)': 400,
-    '180 lm (25W)': 180,
-    '20 lm (4W)': 20,
-    Off: 0,
-};
-
-// ref for solar irradiances: https://en.wikipedia.org/wiki/Lux
-const hemiLuminousIrradiances = {
-    '0.0001 lx (Moonless Night)': 0.0001,
-    '0.002 lx (Night Airglow)': 0.002,
-    '0.5 lx (Full Moon)': 0.5,
-    '3.4 lx (City Twilight)': 3.4,
-    '50 lx (Living Room)': 50,
-    '100 lx (Very Overcast)': 100,
-    '350 lx (Office Room)': 350,
-    '400 lx (Sunrise/Sunset)': 400,
-    '1000 lx (Overcast)': 1000,
-    '18000 lx (Daylight)': 18000,
-    '50000 lx (Direct Sun)': 50000,
-};
-
-const params = {
-    shadows: true,
-    exposure: 0.68,
-    bulbPower: Object.keys(bulbLuminousPowers)[4],
-    hemiIrradiance: Object.keys(hemiLuminousIrradiances)[0],
-};
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.x = -4;
-    camera.position.z = 4;
-    camera.position.y = 2;
-
-    scene = new THREE.Scene();
-
-    const bulbGeometry = new THREE.SphereGeometry(0.02, 16, 8);
-    bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);
-
-    bulbMat = new THREE.MeshStandardMaterial({
-        emissive: 0xffffee,
-        emissiveIntensity: 1,
-        color: 0x000000,
-    });
-    bulbLight.add(new THREE.Mesh(bulbGeometry, bulbMat));
-    bulbLight.position.set(0, 2, 0);
-    bulbLight.castShadow = true;
-    scene.add(bulbLight);
-
-    hemiLight = new THREE.HemisphereLight(0xddeeff, 0x0f0e0d, 0.02);
-    scene.add(hemiLight);
-
-    floorMat = new THREE.MeshStandardMaterial({
-        roughness: 0.8,
-        color: 0xffffff,
-        metalness: 0.2,
-        bumpScale: 1,
-    });
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 4;
-        map.repeat.set(10, 24);
-        map.colorSpace = THREE.SRGBColorSpace;
-        floorMat.map = map;
-        floorMat.needsUpdate = true;
-    });
-    textureLoader.load('textures/hardwood2_bump.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 4;
-        map.repeat.set(10, 24);
-        floorMat.bumpMap = map;
-        floorMat.needsUpdate = true;
-    });
-    textureLoader.load('textures/hardwood2_roughness.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 4;
-        map.repeat.set(10, 24);
-        floorMat.roughnessMap = map;
-        floorMat.needsUpdate = true;
-    });
-
-    cubeMat = new THREE.MeshStandardMaterial({
-        roughness: 0.7,
-        color: 0xffffff,
-        bumpScale: 1,
-        metalness: 0.2,
-    });
-    textureLoader.load('textures/brick_diffuse.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 4;
-        map.repeat.set(1, 1);
-        map.colorSpace = THREE.SRGBColorSpace;
-        cubeMat.map = map;
-        cubeMat.needsUpdate = true;
-    });
-    textureLoader.load('textures/brick_bump.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 4;
-        map.repeat.set(1, 1);
-        cubeMat.bumpMap = map;
-        cubeMat.needsUpdate = true;
-    });
-
-    ballMat = new THREE.MeshStandardMaterial({
-        color: 0xffffff,
-        roughness: 0.5,
-        metalness: 1.0,
-    });
-    textureLoader.load('textures/planets/earth_atmos_2048.jpg', function (map) {
-        map.anisotropy = 4;
-        map.colorSpace = THREE.SRGBColorSpace;
-        ballMat.map = map;
-        ballMat.needsUpdate = true;
-    });
-    textureLoader.load('textures/planets/earth_specular_2048.jpg', function (map) {
-        map.anisotropy = 4;
-        map.colorSpace = THREE.SRGBColorSpace;
-        ballMat.metalnessMap = map;
-        ballMat.needsUpdate = true;
-    });
-
-    const floorGeometry = new THREE.PlaneGeometry(20, 20);
-    const floorMesh = new THREE.Mesh(floorGeometry, floorMat);
-    floorMesh.receiveShadow = true;
-    floorMesh.rotation.x = -Math.PI / 2.0;
-    scene.add(floorMesh);
-
-    const ballGeometry = new THREE.SphereGeometry(0.25, 32, 32);
-    const ballMesh = new THREE.Mesh(ballGeometry, ballMat);
-    ballMesh.position.set(1, 0.25, 1);
-    ballMesh.rotation.y = Math.PI;
-    ballMesh.castShadow = true;
-    scene.add(ballMesh);
-
-    const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
-    const boxMesh = new THREE.Mesh(boxGeometry, cubeMat);
-    boxMesh.position.set(-0.5, 0.25, -1);
-    boxMesh.castShadow = true;
-    scene.add(boxMesh);
-
-    const boxMesh2 = new THREE.Mesh(boxGeometry, cubeMat);
-    boxMesh2.position.set(0, 0.25, -5);
-    boxMesh2.castShadow = true;
-    scene.add(boxMesh2);
-
-    const boxMesh3 = new THREE.Mesh(boxGeometry, cubeMat);
-    boxMesh3.position.set(7, 0.25, 0);
-    boxMesh3.castShadow = true;
-    scene.add(boxMesh3);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 20;
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'hemiIrradiance', Object.keys(hemiLuminousIrradiances));
-    gui.add(params, 'bulbPower', Object.keys(bulbLuminousPowers));
-    gui.add(params, 'exposure', 0, 1);
-    gui.add(params, 'shadows');
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.toneMappingExposure = Math.pow(params.exposure, 5.0); // to allow for very bright scenes.
-    renderer.shadowMap.enabled = params.shadows;
-    bulbLight.castShadow = params.shadows;
-
-    if (params.shadows !== previousShadowMap) {
-        ballMat.needsUpdate = true;
-        cubeMat.needsUpdate = true;
-        floorMat.needsUpdate = true;
-        previousShadowMap = params.shadows;
-    }
-
-    bulbLight.power = bulbLuminousPowers[params.bulbPower];
-    bulbMat.emissiveIntensity = bulbLight.intensity / Math.pow(0.02, 2.0); // convert from intensity to irradiance at bulb surface
-
-    hemiLight.intensity = hemiLuminousIrradiances[params.hemiIrradiance];
-    const time = Date.now() * 0.0005;
-
-    bulbLight.position.y = Math.cos(time) * 0.75 + 1.25;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_lights_pointlights.ts b/examples-testing/examples/webgl_lights_pointlights.ts
deleted file mode 100644
index ea95070ce..000000000
--- a/examples-testing/examples/webgl_lights_pointlights.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-
-let camera, scene, renderer, light1, light2, light3, light4, object, stats;
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 100;
-
-    scene = new THREE.Scene();
-
-    //model
-
-    const loader = new OBJLoader();
-    loader.load('models/obj/walt/WaltHead.obj', function (obj) {
-        object = obj;
-        object.scale.multiplyScalar(0.8);
-        object.position.y = -30;
-        scene.add(object);
-    });
-
-    const sphere = new THREE.SphereGeometry(0.5, 16, 8);
-
-    //lights
-
-    light1 = new THREE.PointLight(0xff0040, 400);
-    light1.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xff0040 })));
-    scene.add(light1);
-
-    light2 = new THREE.PointLight(0x0040ff, 400);
-    light2.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0x0040ff })));
-    scene.add(light2);
-
-    light3 = new THREE.PointLight(0x80ff80, 400);
-    light3.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0x80ff80 })));
-    scene.add(light3);
-
-    light4 = new THREE.PointLight(0xffaa00, 400);
-    light4.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xffaa00 })));
-    scene.add(light4);
-
-    //renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.0005;
-    const delta = clock.getDelta();
-
-    if (object) object.rotation.y -= 0.5 * delta;
-
-    light1.position.x = Math.sin(time * 0.7) * 30;
-    light1.position.y = Math.cos(time * 0.5) * 40;
-    light1.position.z = Math.cos(time * 0.3) * 30;
-
-    light2.position.x = Math.cos(time * 0.3) * 30;
-    light2.position.y = Math.sin(time * 0.5) * 40;
-    light2.position.z = Math.sin(time * 0.7) * 30;
-
-    light3.position.x = Math.sin(time * 0.7) * 30;
-    light3.position.y = Math.cos(time * 0.3) * 40;
-    light3.position.z = Math.sin(time * 0.5) * 30;
-
-    light4.position.x = Math.sin(time * 0.3) * 30;
-    light4.position.y = Math.cos(time * 0.7) * 40;
-    light4.position.z = Math.sin(time * 0.5) * 30;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lights_rectarealight.ts b/examples-testing/examples/webgl_lights_rectarealight.ts
deleted file mode 100644
index b841fa6b5..000000000
--- a/examples-testing/examples/webgl_lights_rectarealight.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
-import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
-
-let renderer, scene, camera;
-let stats, meshKnot;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animation);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 5, -15);
-
-    scene = new THREE.Scene();
-
-    RectAreaLightUniformsLib.init();
-
-    const rectLight1 = new THREE.RectAreaLight(0xff0000, 5, 4, 10);
-    rectLight1.position.set(-5, 5, 5);
-    scene.add(rectLight1);
-
-    const rectLight2 = new THREE.RectAreaLight(0x00ff00, 5, 4, 10);
-    rectLight2.position.set(0, 5, 5);
-    scene.add(rectLight2);
-
-    const rectLight3 = new THREE.RectAreaLight(0x0000ff, 5, 4, 10);
-    rectLight3.position.set(5, 5, 5);
-    scene.add(rectLight3);
-
-    scene.add(new RectAreaLightHelper(rectLight1));
-    scene.add(new RectAreaLightHelper(rectLight2));
-    scene.add(new RectAreaLightHelper(rectLight3));
-
-    const geoFloor = new THREE.BoxGeometry(2000, 0.1, 2000);
-    const matStdFloor = new THREE.MeshStandardMaterial({ color: 0xbcbcbc, roughness: 0.1, metalness: 0 });
-    const mshStdFloor = new THREE.Mesh(geoFloor, matStdFloor);
-    scene.add(mshStdFloor);
-
-    const geoKnot = new THREE.TorusKnotGeometry(1.5, 0.5, 200, 16);
-    const matKnot = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0, metalness: 0 });
-    meshKnot = new THREE.Mesh(geoKnot, matKnot);
-    meshKnot.position.set(0, 5, 0);
-    scene.add(meshKnot);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.copy(meshKnot.position);
-    controls.update();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-function animation(time) {
-    meshKnot.rotation.y = time / 1000;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_lights_spotlight.ts b/examples-testing/examples/webgl_lights_spotlight.ts
deleted file mode 100644
index 894abaf6f..000000000
--- a/examples-testing/examples/webgl_lights_spotlight.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let renderer, scene, camera;
-
-let spotLight, lightHelper;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(7, 4, 1);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.maxPolarAngle = Math.PI / 2;
-    controls.target.set(0, 1, 0);
-    controls.update();
-
-    const ambient = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.15);
-    scene.add(ambient);
-
-    const loader = new THREE.TextureLoader().setPath('textures/');
-    const filenames = ['disturb.jpg', 'colors.png', 'uv_grid_opengl.jpg'];
-
-    const textures = { none: null };
-
-    for (let i = 0; i < filenames.length; i++) {
-        const filename = filenames[i];
-
-        const texture = loader.load(filename);
-        texture.minFilter = THREE.LinearFilter;
-        texture.magFilter = THREE.LinearFilter;
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        textures[filename] = texture;
-    }
-
-    spotLight = new THREE.SpotLight(0xffffff, 100);
-    spotLight.position.set(2.5, 5, 2.5);
-    spotLight.angle = Math.PI / 6;
-    spotLight.penumbra = 1;
-    spotLight.decay = 2;
-    spotLight.distance = 0;
-    spotLight.map = textures['disturb.jpg'];
-
-    spotLight.castShadow = true;
-    spotLight.shadow.mapSize.width = 1024;
-    spotLight.shadow.mapSize.height = 1024;
-    spotLight.shadow.camera.near = 1;
-    spotLight.shadow.camera.far = 10;
-    spotLight.shadow.focus = 1;
-    scene.add(spotLight);
-
-    lightHelper = new THREE.SpotLightHelper(spotLight);
-    scene.add(lightHelper);
-
-    //
-
-    const geometry = new THREE.PlaneGeometry(200, 200);
-    const material = new THREE.MeshLambertMaterial({ color: 0xbcbcbc });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    mesh.position.set(0, -1, 0);
-    mesh.rotation.x = -Math.PI / 2;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    //
-
-    new PLYLoader().load('models/ply/binary/Lucy100k.ply', function (geometry) {
-        geometry.scale(0.0024, 0.0024, 0.0024);
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshLambertMaterial();
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.rotation.y = -Math.PI / 2;
-        mesh.position.y = 0.8;
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-        scene.add(mesh);
-    });
-
-    window.addEventListener('resize', onWindowResize);
-
-    // GUI
-
-    const gui = new GUI();
-
-    const params = {
-        map: textures['disturb.jpg'],
-        color: spotLight.color.getHex(),
-        intensity: spotLight.intensity,
-        distance: spotLight.distance,
-        angle: spotLight.angle,
-        penumbra: spotLight.penumbra,
-        decay: spotLight.decay,
-        focus: spotLight.shadow.focus,
-        shadows: true,
-    };
-
-    gui.add(params, 'map', textures).onChange(function (val) {
-        spotLight.map = val;
-    });
-
-    gui.addColor(params, 'color').onChange(function (val) {
-        spotLight.color.setHex(val);
-    });
-
-    gui.add(params, 'intensity', 0, 500).onChange(function (val) {
-        spotLight.intensity = val;
-    });
-
-    gui.add(params, 'distance', 0, 20).onChange(function (val) {
-        spotLight.distance = val;
-    });
-
-    gui.add(params, 'angle', 0, Math.PI / 3).onChange(function (val) {
-        spotLight.angle = val;
-    });
-
-    gui.add(params, 'penumbra', 0, 1).onChange(function (val) {
-        spotLight.penumbra = val;
-    });
-
-    gui.add(params, 'decay', 1, 2).onChange(function (val) {
-        spotLight.decay = val;
-    });
-
-    gui.add(params, 'focus', 0, 1).onChange(function (val) {
-        spotLight.shadow.focus = val;
-    });
-
-    gui.add(params, 'shadows').onChange(function (val) {
-        renderer.shadowMap.enabled = val;
-
-        scene.traverse(function (child) {
-            if (child.material) {
-                child.material.needsUpdate = true;
-            }
-        });
-    });
-
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = performance.now() / 3000;
-
-    spotLight.position.x = Math.cos(time) * 2.5;
-    spotLight.position.z = Math.sin(time) * 2.5;
-
-    lightHelper.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lights_spotlights.ts b/examples-testing/examples/webgl_lights_spotlights.ts
deleted file mode 100644
index 70caf5a58..000000000
--- a/examples-testing/examples/webgl_lights_spotlights.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as THREE from 'three';
-import TWEEN from 'three/addons/libs/tween.module.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-const renderer = new THREE.WebGLRenderer({ antialias: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.setAnimationLoop(animate);
-
-const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
-
-const controls = new OrbitControls(camera, renderer.domElement);
-
-const scene = new THREE.Scene();
-
-const matFloor = new THREE.MeshPhongMaterial({ color: 0x808080 });
-const matBox = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
-
-const geoFloor = new THREE.PlaneGeometry(100, 100);
-const geoBox = new THREE.BoxGeometry(0.3, 0.1, 0.2);
-
-const mshFloor = new THREE.Mesh(geoFloor, matFloor);
-mshFloor.rotation.x = -Math.PI * 0.5;
-const mshBox = new THREE.Mesh(geoBox, matBox);
-
-const ambient = new THREE.AmbientLight(0x444444);
-
-const spotLight1 = createSpotlight(0xff7f00);
-const spotLight2 = createSpotlight(0x00ff7f);
-const spotLight3 = createSpotlight(0x7f00ff);
-
-let lightHelper1, lightHelper2, lightHelper3;
-
-function init() {
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-    camera.position.set(4.6, 2.2, -2.1);
-
-    spotLight1.position.set(1.5, 4, 4.5);
-    spotLight2.position.set(0, 4, 3.5);
-    spotLight3.position.set(-1.5, 4, 4.5);
-
-    lightHelper1 = new THREE.SpotLightHelper(spotLight1);
-    lightHelper2 = new THREE.SpotLightHelper(spotLight2);
-    lightHelper3 = new THREE.SpotLightHelper(spotLight3);
-
-    mshFloor.receiveShadow = true;
-    mshFloor.position.set(0, -0.05, 0);
-
-    mshBox.castShadow = true;
-    mshBox.receiveShadow = true;
-    mshBox.position.set(0, 0.5, 0);
-
-    scene.add(mshFloor);
-    scene.add(mshBox);
-    scene.add(ambient);
-    scene.add(spotLight1, spotLight2, spotLight3);
-    scene.add(lightHelper1, lightHelper2, lightHelper3);
-
-    document.body.appendChild(renderer.domElement);
-    window.addEventListener('resize', onWindowResize);
-
-    controls.target.set(0, 0.5, 0);
-    controls.maxPolarAngle = Math.PI / 2;
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-    controls.update();
-}
-
-function createSpotlight(color) {
-    const newObj = new THREE.SpotLight(color, 10);
-
-    newObj.castShadow = true;
-    newObj.angle = 0.3;
-    newObj.penumbra = 0.2;
-    newObj.decay = 2;
-    newObj.distance = 50;
-
-    return newObj;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function tween(light) {
-    new TWEEN.Tween(light)
-        .to(
-            {
-                angle: Math.random() * 0.7 + 0.1,
-                penumbra: Math.random() + 1,
-            },
-            Math.random() * 3000 + 2000,
-        )
-        .easing(TWEEN.Easing.Quadratic.Out)
-        .start();
-
-    new TWEEN.Tween(light.position)
-        .to(
-            {
-                x: Math.random() * 3 - 1.5,
-                y: Math.random() * 1 + 1.5,
-                z: Math.random() * 3 - 1.5,
-            },
-            Math.random() * 3000 + 2000,
-        )
-        .easing(TWEEN.Easing.Quadratic.Out)
-        .start();
-}
-
-function updateTweens() {
-    tween(spotLight1);
-    tween(spotLight2);
-    tween(spotLight3);
-
-    setTimeout(updateTweens, 5000);
-}
-
-function animate() {
-    TWEEN.update();
-
-    if (lightHelper1) lightHelper1.update();
-    if (lightHelper2) lightHelper2.update();
-    if (lightHelper3) lightHelper3.update();
-
-    renderer.render(scene, camera);
-}
-
-init();
-updateTweens();
diff --git a/examples-testing/examples/webgl_lines_colors.ts b/examples-testing/examples/webgl_lines_colors.ts
deleted file mode 100644
index 9da19ee2e..000000000
--- a/examples-testing/examples/webgl_lines_colors.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import * as THREE from 'three';
-
-import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(33, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const hilbertPoints = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 200.0, 1, 0, 1, 2, 3, 4, 5, 6, 7);
-
-    const geometry1 = new THREE.BufferGeometry();
-    const geometry2 = new THREE.BufferGeometry();
-    const geometry3 = new THREE.BufferGeometry();
-
-    const subdivisions = 6;
-
-    let vertices = [];
-    let colors1 = [];
-    let colors2 = [];
-    let colors3 = [];
-
-    const point = new THREE.Vector3();
-    const color = new THREE.Color();
-
-    const spline = new THREE.CatmullRomCurve3(hilbertPoints);
-
-    for (let i = 0; i < hilbertPoints.length * subdivisions; i++) {
-        const t = i / (hilbertPoints.length * subdivisions);
-        spline.getPoint(t, point);
-
-        vertices.push(point.x, point.y, point.z);
-
-        color.setHSL(0.6, 1.0, Math.max(0, -point.x / 200) + 0.5, THREE.SRGBColorSpace);
-        colors1.push(color.r, color.g, color.b);
-
-        color.setHSL(0.9, 1.0, Math.max(0, -point.y / 200) + 0.5, THREE.SRGBColorSpace);
-        colors2.push(color.r, color.g, color.b);
-
-        color.setHSL(i / (hilbertPoints.length * subdivisions), 1.0, 0.5, THREE.SRGBColorSpace);
-        colors3.push(color.r, color.g, color.b);
-    }
-
-    geometry1.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry2.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry3.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    geometry1.setAttribute('color', new THREE.Float32BufferAttribute(colors1, 3));
-    geometry2.setAttribute('color', new THREE.Float32BufferAttribute(colors2, 3));
-    geometry3.setAttribute('color', new THREE.Float32BufferAttribute(colors3, 3));
-
-    //
-
-    const geometry4 = new THREE.BufferGeometry();
-    const geometry5 = new THREE.BufferGeometry();
-    const geometry6 = new THREE.BufferGeometry();
-
-    vertices = [];
-    colors1 = [];
-    colors2 = [];
-    colors3 = [];
-
-    for (let i = 0; i < hilbertPoints.length; i++) {
-        const point = hilbertPoints[i];
-
-        vertices.push(point.x, point.y, point.z);
-
-        color.setHSL(0.6, 1.0, Math.max(0, (200 - hilbertPoints[i].x) / 400) * 0.5 + 0.5, THREE.SRGBColorSpace);
-        colors1.push(color.r, color.g, color.b);
-
-        color.setHSL(0.3, 1.0, Math.max(0, (200 + hilbertPoints[i].x) / 400) * 0.5, THREE.SRGBColorSpace);
-        colors2.push(color.r, color.g, color.b);
-
-        color.setHSL(i / hilbertPoints.length, 1.0, 0.5, THREE.SRGBColorSpace);
-        colors3.push(color.r, color.g, color.b);
-    }
-
-    geometry4.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry5.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-    geometry6.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    geometry4.setAttribute('color', new THREE.Float32BufferAttribute(colors1, 3));
-    geometry5.setAttribute('color', new THREE.Float32BufferAttribute(colors2, 3));
-    geometry6.setAttribute('color', new THREE.Float32BufferAttribute(colors3, 3));
-
-    // Create lines and add to scene
-
-    const material = new THREE.LineBasicMaterial({ color: 0xffffff, vertexColors: true });
-
-    let line, p;
-    const scale = 0.3,
-        d = 225;
-
-    const parameters = [
-        [material, scale * 1.5, [-d, -d / 2, 0], geometry1],
-        [material, scale * 1.5, [0, -d / 2, 0], geometry2],
-        [material, scale * 1.5, [d, -d / 2, 0], geometry3],
-
-        [material, scale * 1.5, [-d, d / 2, 0], geometry4],
-        [material, scale * 1.5, [0, d / 2, 0], geometry5],
-        [material, scale * 1.5, [d, d / 2, 0], geometry6],
-    ];
-
-    for (let i = 0; i < parameters.length; i++) {
-        p = parameters[i];
-        line = new THREE.Line(p[3], p[0]);
-        line.scale.x = line.scale.y = line.scale.z = p[1];
-        line.position.x = p[2][0];
-        line.position.y = p[2][1];
-        line.position.z = p[2][2];
-        scene.add(line);
-    }
-
-    //
-
-    document.body.style.touchAction = 'none';
-    document.body.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY + 200 - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    const time = Date.now() * 0.0005;
-
-    for (let i = 0; i < scene.children.length; i++) {
-        const object = scene.children[i];
-
-        if (object.isLine) {
-            object.rotation.y = time * (i % 2 ? 1 : -1);
-        }
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lines_dashed.ts b/examples-testing/examples/webgl_lines_dashed.ts
deleted file mode 100644
index 4849e7c3a..000000000
--- a/examples-testing/examples/webgl_lines_dashed.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
-
-let renderer, scene, camera, stats;
-const objects = [];
-
-const WIDTH = window.innerWidth,
-    HEIGHT = window.innerHeight;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, WIDTH / HEIGHT, 1, 200);
-    camera.position.z = 150;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x111111);
-    scene.fog = new THREE.Fog(0x111111, 150, 200);
-
-    const subdivisions = 6;
-    const recursion = 1;
-
-    const points = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 25.0, recursion, 0, 1, 2, 3, 4, 5, 6, 7);
-    const spline = new THREE.CatmullRomCurve3(points);
-
-    const samples = spline.getPoints(points.length * subdivisions);
-    const geometrySpline = new THREE.BufferGeometry().setFromPoints(samples);
-
-    const line = new THREE.Line(
-        geometrySpline,
-        new THREE.LineDashedMaterial({ color: 0xffffff, dashSize: 1, gapSize: 0.5 }),
-    );
-    line.computeLineDistances();
-
-    objects.push(line);
-    scene.add(line);
-
-    const geometryBox = box(50, 50, 50);
-
-    const lineSegments = new THREE.LineSegments(
-        geometryBox,
-        new THREE.LineDashedMaterial({ color: 0xffaa00, dashSize: 3, gapSize: 1 }),
-    );
-    lineSegments.computeLineDistances();
-
-    objects.push(lineSegments);
-    scene.add(lineSegments);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(WIDTH, HEIGHT);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function box(width, height, depth) {
-    (width = width * 0.5), (height = height * 0.5), (depth = depth * 0.5);
-
-    const geometry = new THREE.BufferGeometry();
-    const position = [];
-
-    position.push(
-        -width,
-        -height,
-        -depth,
-        -width,
-        height,
-        -depth,
-
-        -width,
-        height,
-        -depth,
-        width,
-        height,
-        -depth,
-
-        width,
-        height,
-        -depth,
-        width,
-        -height,
-        -depth,
-
-        width,
-        -height,
-        -depth,
-        -width,
-        -height,
-        -depth,
-
-        -width,
-        -height,
-        depth,
-        -width,
-        height,
-        depth,
-
-        -width,
-        height,
-        depth,
-        width,
-        height,
-        depth,
-
-        width,
-        height,
-        depth,
-        width,
-        -height,
-        depth,
-
-        width,
-        -height,
-        depth,
-        -width,
-        -height,
-        depth,
-
-        -width,
-        -height,
-        -depth,
-        -width,
-        -height,
-        depth,
-
-        -width,
-        height,
-        -depth,
-        -width,
-        height,
-        depth,
-
-        width,
-        height,
-        -depth,
-        width,
-        height,
-        depth,
-
-        width,
-        -height,
-        -depth,
-        width,
-        -height,
-        depth,
-    );
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
-
-    return geometry;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.001;
-
-    scene.traverse(function (object) {
-        if (object.isLine) {
-            object.rotation.x = 0.25 * time;
-            object.rotation.y = 0.25 * time;
-        }
-    });
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lines_fat.ts b/examples-testing/examples/webgl_lines_fat.ts
deleted file mode 100644
index 34c2e2d5e..000000000
--- a/examples-testing/examples/webgl_lines_fat.ts
+++ /dev/null
@@ -1,251 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Line2 } from 'three/addons/lines/Line2.js';
-import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
-import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
-import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
-
-let line, renderer, scene, camera, camera2, controls;
-let line1;
-let matLine, matLineBasic, matLineDashed;
-let stats, gpuPanel;
-let gui;
-
-// viewport
-let insetWidth;
-let insetHeight;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setClearColor(0x000000, 0.0);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(-40, 0, 60);
-
-    camera2 = new THREE.PerspectiveCamera(40, 1, 1, 1000);
-    camera2.position.copy(camera.position);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 10;
-    controls.maxDistance = 500;
-
-    // Position and THREE.Color Data
-
-    const positions = [];
-    const colors = [];
-
-    const points = GeometryUtils.hilbert3D(new THREE.Vector3(0, 0, 0), 20.0, 1, 0, 1, 2, 3, 4, 5, 6, 7);
-
-    const spline = new THREE.CatmullRomCurve3(points);
-    const divisions = Math.round(12 * points.length);
-    const point = new THREE.Vector3();
-    const color = new THREE.Color();
-
-    for (let i = 0, l = divisions; i < l; i++) {
-        const t = i / l;
-
-        spline.getPoint(t, point);
-        positions.push(point.x, point.y, point.z);
-
-        color.setHSL(t, 1.0, 0.5, THREE.SRGBColorSpace);
-        colors.push(color.r, color.g, color.b);
-    }
-
-    // Line2 ( LineGeometry, LineMaterial )
-
-    const geometry = new LineGeometry();
-    geometry.setPositions(positions);
-    geometry.setColors(colors);
-
-    matLine = new LineMaterial({
-        color: 0xffffff,
-        linewidth: 5, // in world units with size attenuation, pixels otherwise
-        vertexColors: true,
-
-        dashed: false,
-        alphaToCoverage: true,
-    });
-
-    line = new Line2(geometry, matLine);
-    line.computeLineDistances();
-    line.scale.set(1, 1, 1);
-    scene.add(line);
-
-    // THREE.Line ( THREE.BufferGeometry, THREE.LineBasicMaterial ) - rendered with gl.LINE_STRIP
-
-    const geo = new THREE.BufferGeometry();
-    geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    matLineBasic = new THREE.LineBasicMaterial({ vertexColors: true });
-    matLineDashed = new THREE.LineDashedMaterial({ vertexColors: true, scale: 2, dashSize: 1, gapSize: 1 });
-
-    line1 = new THREE.Line(geo, matLineBasic);
-    line1.computeLineDistances();
-    line1.visible = false;
-    scene.add(line1);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    onWindowResize();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    gpuPanel = new GPUStatsPanel(renderer.getContext());
-    stats.addPanel(gpuPanel);
-    stats.showPanel(0);
-
-    initGui();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    insetWidth = window.innerHeight / 4; // square
-    insetHeight = window.innerHeight / 4;
-
-    camera2.aspect = insetWidth / insetHeight;
-    camera2.updateProjectionMatrix();
-}
-
-function animate() {
-    // main scene
-
-    renderer.setClearColor(0x000000, 0);
-
-    renderer.setViewport(0, 0, window.innerWidth, window.innerHeight);
-
-    controls.update();
-
-    gpuPanel.startQuery();
-    renderer.render(scene, camera);
-    gpuPanel.endQuery();
-
-    // inset scene
-
-    renderer.setClearColor(0x222222, 1);
-
-    renderer.clearDepth(); // important!
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(20, 20, insetWidth, insetHeight);
-
-    renderer.setViewport(20, 20, insetWidth, insetHeight);
-
-    camera2.position.copy(camera.position);
-    camera2.quaternion.copy(camera.quaternion);
-
-    renderer.render(scene, camera2);
-
-    renderer.setScissorTest(false);
-
-    stats.update();
-}
-
-//
-
-function initGui() {
-    gui = new GUI();
-
-    const param = {
-        'line type': 0,
-        'world units': false,
-        width: 5,
-        alphaToCoverage: true,
-        dashed: false,
-        'dash scale': 1,
-        'dash / gap': 1,
-    };
-
-    gui.add(param, 'line type', { LineGeometry: 0, 'gl.LINE': 1 }).onChange(function (val) {
-        switch (val) {
-            case 0:
-                line.visible = true;
-
-                line1.visible = false;
-
-                break;
-
-            case 1:
-                line.visible = false;
-
-                line1.visible = true;
-
-                break;
-        }
-    });
-
-    gui.add(param, 'world units').onChange(function (val) {
-        matLine.worldUnits = val;
-        matLine.needsUpdate = true;
-    });
-
-    gui.add(param, 'width', 1, 10).onChange(function (val) {
-        matLine.linewidth = val;
-    });
-
-    gui.add(param, 'alphaToCoverage').onChange(function (val) {
-        matLine.alphaToCoverage = val;
-    });
-
-    gui.add(param, 'dashed').onChange(function (val) {
-        matLine.dashed = val;
-        line1.material = val ? matLineDashed : matLineBasic;
-    });
-
-    gui.add(param, 'dash scale', 0.5, 2, 0.1).onChange(function (val) {
-        matLine.dashScale = val;
-        matLineDashed.scale = val;
-    });
-
-    gui.add(param, 'dash / gap', { '2 : 1': 0, '1 : 1': 1, '1 : 2': 2 }).onChange(function (val) {
-        switch (val) {
-            case 0:
-                matLine.dashSize = 2;
-                matLine.gapSize = 1;
-
-                matLineDashed.dashSize = 2;
-                matLineDashed.gapSize = 1;
-
-                break;
-
-            case 1:
-                matLine.dashSize = 1;
-                matLine.gapSize = 1;
-
-                matLineDashed.dashSize = 1;
-                matLineDashed.gapSize = 1;
-
-                break;
-
-            case 2:
-                matLine.dashSize = 1;
-                matLine.gapSize = 2;
-
-                matLineDashed.dashSize = 1;
-                matLineDashed.gapSize = 2;
-
-                break;
-        }
-    });
-}
diff --git a/examples-testing/examples/webgl_lines_fat_raycasting.ts b/examples-testing/examples/webgl_lines_fat_raycasting.ts
deleted file mode 100644
index 75cef6204..000000000
--- a/examples-testing/examples/webgl_lines_fat_raycasting.ts
+++ /dev/null
@@ -1,294 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
-import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
-import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
-import { Line2 } from 'three/addons/lines/Line2.js';
-import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
-
-let line, thresholdLine, segments, thresholdSegments;
-let renderer, scene, camera, controls;
-let sphereInter, sphereOnLine;
-let stats, gpuPanel;
-let gui;
-let clock;
-
-const color = new THREE.Color();
-
-const pointer = new THREE.Vector2(Infinity, Infinity);
-
-const raycaster = new THREE.Raycaster();
-
-raycaster.params.Line2 = {};
-raycaster.params.Line2.threshold = 0;
-
-const matLine = new LineMaterial({
-    color: 0xffffff,
-    linewidth: 1, // in world units with size attenuation, pixels otherwise
-    worldUnits: true,
-    vertexColors: true,
-
-    alphaToCoverage: true,
-});
-
-const matThresholdLine = new LineMaterial({
-    color: 0xffffff,
-    linewidth: matLine.linewidth, // in world units with size attenuation, pixels otherwise
-    worldUnits: true,
-    // vertexColors: true,
-    transparent: true,
-    opacity: 0.2,
-    depthTest: false,
-    visible: false,
-});
-
-const params = {
-    'line type': 0,
-    'world units': matLine.worldUnits,
-    'visualize threshold': matThresholdLine.visible,
-    width: matLine.linewidth,
-    alphaToCoverage: matLine.alphaToCoverage,
-    threshold: raycaster.params.Line2.threshold,
-    translation: raycaster.params.Line2.threshold,
-    animate: true,
-};
-
-init();
-
-function init() {
-    clock = new THREE.Clock();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setClearColor(0x000000, 0.0);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(-40, 0, 60);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 10;
-    controls.maxDistance = 500;
-
-    const sphereGeometry = new THREE.SphereGeometry(0.25, 8, 4);
-    const sphereInterMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, depthTest: false });
-    const sphereOnLineMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, depthTest: false });
-
-    sphereInter = new THREE.Mesh(sphereGeometry, sphereInterMaterial);
-    sphereOnLine = new THREE.Mesh(sphereGeometry, sphereOnLineMaterial);
-    sphereInter.visible = false;
-    sphereOnLine.visible = false;
-    sphereInter.renderOrder = 10;
-    sphereOnLine.renderOrder = 10;
-    scene.add(sphereInter);
-    scene.add(sphereOnLine);
-
-    // Position and THREE.Color Data
-
-    const positions = [];
-    const colors = [];
-    const points = [];
-    for (let i = -50; i < 50; i++) {
-        const t = i / 3;
-        points.push(new THREE.Vector3(t * Math.sin(2 * t), t, t * Math.cos(2 * t)));
-    }
-
-    const spline = new THREE.CatmullRomCurve3(points);
-    const divisions = Math.round(3 * points.length);
-    const point = new THREE.Vector3();
-    const color = new THREE.Color();
-
-    for (let i = 0, l = divisions; i < l; i++) {
-        const t = i / l;
-
-        spline.getPoint(t, point);
-        positions.push(point.x, point.y, point.z);
-
-        color.setHSL(t, 1.0, 0.5, THREE.SRGBColorSpace);
-        colors.push(color.r, color.g, color.b);
-    }
-
-    const lineGeometry = new LineGeometry();
-    lineGeometry.setPositions(positions);
-    lineGeometry.setColors(colors);
-
-    const segmentsGeometry = new LineSegmentsGeometry();
-    segmentsGeometry.setPositions(positions);
-    segmentsGeometry.setColors(colors);
-
-    segments = new LineSegments2(segmentsGeometry, matLine);
-    segments.computeLineDistances();
-    segments.scale.set(1, 1, 1);
-    scene.add(segments);
-    segments.visible = false;
-
-    thresholdSegments = new LineSegments2(segmentsGeometry, matThresholdLine);
-    thresholdSegments.computeLineDistances();
-    thresholdSegments.scale.set(1, 1, 1);
-    scene.add(thresholdSegments);
-    thresholdSegments.visible = false;
-
-    line = new Line2(lineGeometry, matLine);
-    line.computeLineDistances();
-    line.scale.set(1, 1, 1);
-    scene.add(line);
-
-    thresholdLine = new Line2(lineGeometry, matThresholdLine);
-    thresholdLine.computeLineDistances();
-    thresholdLine.scale.set(1, 1, 1);
-    scene.add(thresholdLine);
-
-    const geo = new THREE.BufferGeometry();
-    geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
-    geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
-
-    //
-
-    document.addEventListener('pointermove', onPointerMove);
-    window.addEventListener('resize', onWindowResize);
-    onWindowResize();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    gpuPanel = new GPUStatsPanel(renderer.getContext());
-    stats.addPanel(gpuPanel);
-    stats.showPanel(0);
-    initGui();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    const obj = line.visible ? line : segments;
-    thresholdLine.position.copy(line.position);
-    thresholdLine.quaternion.copy(line.quaternion);
-    thresholdSegments.position.copy(segments.position);
-    thresholdSegments.quaternion.copy(segments.quaternion);
-
-    if (params.animate) {
-        line.rotation.y += delta * 0.1;
-
-        segments.rotation.y = line.rotation.y;
-    }
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObject(obj);
-
-    if (intersects.length > 0) {
-        sphereInter.visible = true;
-        sphereOnLine.visible = true;
-
-        sphereInter.position.copy(intersects[0].point);
-        sphereOnLine.position.copy(intersects[0].pointOnLine);
-
-        const index = intersects[0].faceIndex;
-        const colors = obj.geometry.getAttribute('instanceColorStart');
-
-        color.fromBufferAttribute(colors, index);
-
-        sphereInter.material.color.copy(color).offsetHSL(0.3, 0, 0);
-        sphereOnLine.material.color.copy(color).offsetHSL(0.7, 0, 0);
-
-        renderer.domElement.style.cursor = 'crosshair';
-    } else {
-        sphereInter.visible = false;
-        sphereOnLine.visible = false;
-        renderer.domElement.style.cursor = '';
-    }
-
-    gpuPanel.startQuery();
-    renderer.render(scene, camera);
-    gpuPanel.endQuery();
-
-    stats.update();
-}
-
-//
-
-function switchLine(val) {
-    switch (val) {
-        case 0:
-            line.visible = true;
-            thresholdLine.visible = true;
-
-            segments.visible = false;
-            thresholdSegments.visible = false;
-
-            break;
-
-        case 1:
-            line.visible = false;
-            thresholdLine.visible = false;
-
-            segments.visible = true;
-            thresholdSegments.visible = true;
-
-            break;
-    }
-}
-
-function initGui() {
-    gui = new GUI();
-
-    gui.add(params, 'line type', { LineGeometry: 0, LineSegmentsGeometry: 1 })
-        .onChange(function (val) {
-            switchLine(val);
-        })
-        .setValue(1);
-
-    gui.add(params, 'world units').onChange(function (val) {
-        matLine.worldUnits = val;
-        matLine.needsUpdate = true;
-
-        matThresholdLine.worldUnits = val;
-        matThresholdLine.needsUpdate = true;
-    });
-
-    gui.add(params, 'visualize threshold').onChange(function (val) {
-        matThresholdLine.visible = val;
-    });
-
-    gui.add(params, 'width', 1, 10).onChange(function (val) {
-        matLine.linewidth = val;
-        matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
-    });
-
-    gui.add(params, 'alphaToCoverage').onChange(function (val) {
-        matLine.alphaToCoverage = val;
-    });
-
-    gui.add(params, 'threshold', 0, 10).onChange(function (val) {
-        raycaster.params.Line2.threshold = val;
-        matThresholdLine.linewidth = matLine.linewidth + raycaster.params.Line2.threshold;
-    });
-
-    gui.add(params, 'translation', 0, 10).onChange(function (val) {
-        line.position.x = val;
-        segments.position.x = val;
-    });
-
-    gui.add(params, 'animate');
-}
diff --git a/examples-testing/examples/webgl_lines_fat_wireframe.ts b/examples-testing/examples/webgl_lines_fat_wireframe.ts
deleted file mode 100644
index 59660ad7e..000000000
--- a/examples-testing/examples/webgl_lines_fat_wireframe.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
-import { Wireframe } from 'three/addons/lines/Wireframe.js';
-import { WireframeGeometry2 } from 'three/addons/lines/WireframeGeometry2.js';
-
-let wireframe, renderer, scene, camera, camera2, controls;
-let wireframe1;
-let matLine, matLineBasic, matLineDashed;
-let stats;
-let gui;
-
-// viewport
-let insetWidth;
-let insetHeight;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setClearColor(0x000000, 0.0);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(-50, 0, 50);
-
-    camera2 = new THREE.PerspectiveCamera(40, 1, 1, 1000);
-    camera2.position.copy(camera.position);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 10;
-    controls.maxDistance = 500;
-
-    // Wireframe ( WireframeGeometry2, LineMaterial )
-
-    let geo = new THREE.IcosahedronGeometry(20, 1);
-
-    const geometry = new WireframeGeometry2(geo);
-
-    matLine = new LineMaterial({
-        color: 0x4080ff,
-        linewidth: 5, // in pixels
-        dashed: false,
-    });
-
-    wireframe = new Wireframe(geometry, matLine);
-    wireframe.computeLineDistances();
-    wireframe.scale.set(1, 1, 1);
-    scene.add(wireframe);
-
-    // Line ( THREE.WireframeGeometry, THREE.LineBasicMaterial ) - rendered with gl.LINE
-
-    geo = new THREE.WireframeGeometry(geo);
-
-    matLineBasic = new THREE.LineBasicMaterial({ color: 0x4080ff });
-    matLineDashed = new THREE.LineDashedMaterial({ scale: 2, dashSize: 1, gapSize: 1 });
-
-    wireframe1 = new THREE.LineSegments(geo, matLineBasic);
-    wireframe1.computeLineDistances();
-    wireframe1.visible = false;
-    scene.add(wireframe1);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    onWindowResize();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    initGui();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    insetWidth = window.innerHeight / 4; // square
-    insetHeight = window.innerHeight / 4;
-
-    camera2.aspect = insetWidth / insetHeight;
-    camera2.updateProjectionMatrix();
-}
-
-function animate() {
-    // main scene
-
-    renderer.setClearColor(0x000000, 0);
-
-    renderer.setViewport(0, 0, window.innerWidth, window.innerHeight);
-
-    renderer.render(scene, camera);
-
-    // inset scene
-
-    renderer.setClearColor(0x222222, 1);
-
-    renderer.clearDepth(); // important!
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(20, 20, insetWidth, insetHeight);
-
-    renderer.setViewport(20, 20, insetWidth, insetHeight);
-
-    camera2.position.copy(camera.position);
-    camera2.quaternion.copy(camera.quaternion);
-
-    renderer.render(scene, camera2);
-
-    renderer.setScissorTest(false);
-
-    stats.update();
-}
-
-//
-
-function initGui() {
-    gui = new GUI();
-
-    const param = {
-        'line type': 0,
-        'width (px)': 5,
-        dashed: false,
-        'dash scale': 1,
-        'dash / gap': 1,
-    };
-
-    gui.add(param, 'line type', { LineGeometry: 0, 'gl.LINE': 1 }).onChange(function (val) {
-        switch (val) {
-            case 0:
-                wireframe.visible = true;
-
-                wireframe1.visible = false;
-
-                break;
-
-            case 1:
-                wireframe.visible = false;
-
-                wireframe1.visible = true;
-
-                break;
-        }
-    });
-
-    gui.add(param, 'width (px)', 1, 10).onChange(function (val) {
-        matLine.linewidth = val;
-    });
-
-    gui.add(param, 'dashed').onChange(function (val) {
-        matLine.dashed = val;
-
-        // dashed is implemented as a defines -- not as a uniform. this could be changed.
-        // ... or THREE.LineDashedMaterial could be implemented as a separate material
-        // temporary hack - renderer should do this eventually
-        if (val) matLine.defines.USE_DASH = '';
-        else delete matLine.defines.USE_DASH;
-        matLine.needsUpdate = true;
-
-        wireframe1.material = val ? matLineDashed : matLineBasic;
-    });
-
-    gui.add(param, 'dash scale', 0.5, 1, 0.1).onChange(function (val) {
-        matLine.dashScale = val;
-        matLineDashed.scale = val;
-    });
-
-    gui.add(param, 'dash / gap', { '2 : 1': 0, '1 : 1': 1, '1 : 2': 2 }).onChange(function (val) {
-        switch (val) {
-            case 0:
-                matLine.dashSize = 2;
-                matLine.gapSize = 1;
-
-                matLineDashed.dashSize = 2;
-                matLineDashed.gapSize = 1;
-
-                break;
-
-            case 1:
-                matLine.dashSize = 1;
-                matLine.gapSize = 1;
-
-                matLineDashed.dashSize = 1;
-                matLineDashed.gapSize = 1;
-
-                break;
-
-            case 2:
-                matLine.dashSize = 1;
-                matLine.gapSize = 2;
-
-                matLineDashed.dashSize = 1;
-                matLineDashed.gapSize = 2;
-
-                break;
-        }
-    });
-}
diff --git a/examples-testing/examples/webgl_loader_3dm.ts b/examples-testing/examples/webgl_loader_3dm.ts
deleted file mode 100644
index 7570306fd..000000000
--- a/examples-testing/examples/webgl_loader_3dm.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-let controls, gui;
-
-init();
-
-function init() {
-    THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(26, -40, 5);
-
-    scene = new THREE.Scene();
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 6);
-    directionalLight.position.set(0, 0, 2);
-    scene.add(directionalLight);
-
-    const loader = new Rhino3dmLoader();
-    //generally, use this for the Library Path: https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1
-    loader.setLibraryPath('jsm/libs/rhino3dm/');
-    loader.load(
-        'models/3dm/Rhino_Logo.3dm',
-        function (object) {
-            scene.add(object);
-            initGUI(object.userData.layers);
-
-            // hide spinner
-            document.getElementById('loader').style.display = 'none';
-        },
-        function (progress) {
-            console.log((progress.loaded / progress.total) * 100 + '%');
-        },
-        function (error) {
-            console.log(error);
-        },
-    );
-
-    controls = new OrbitControls(camera, renderer.domElement);
-
-    window.addEventListener('resize', resize);
-}
-
-function resize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    controls.update();
-    renderer.render(scene, camera);
-}
-
-function initGUI(layers) {
-    gui = new GUI({ title: 'layers' });
-
-    for (let i = 0; i < layers.length; i++) {
-        const layer = layers[i];
-        gui.add(layer, 'visible')
-            .name(layer.name)
-            .onChange(function (val) {
-                const name = this.object.name;
-
-                scene.traverse(function (child) {
-                    if (child.userData.hasOwnProperty('attributes')) {
-                        if ('layerIndex' in child.userData.attributes) {
-                            const layerName = layers[child.userData.attributes.layerIndex].name;
-
-                            if (layerName === name) {
-                                child.visible = val;
-                                layer.visible = val;
-                            }
-                        }
-                    }
-                });
-            });
-    }
-}
diff --git a/examples-testing/examples/webgl_loader_3ds.ts b/examples-testing/examples/webgl_loader_3ds.ts
deleted file mode 100644
index 10ce34076..000000000
--- a/examples-testing/examples/webgl_loader_3ds.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';
-
-let container, controls;
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-    scene.add(new THREE.AmbientLight(0xffffff, 3));
-
-    const directionalLight = new THREE.DirectionalLight(0xffeedd, 3);
-    directionalLight.position.set(0, 0, 2);
-    scene.add(directionalLight);
-
-    //3ds files dont store normal maps
-    const normal = new THREE.TextureLoader().load('models/3ds/portalgun/textures/normal.jpg');
-
-    const loader = new TDSLoader();
-    loader.setResourcePath('models/3ds/portalgun/textures/');
-    loader.load('models/3ds/portalgun/portalgun.3ds', function (object) {
-        object.traverse(function (child) {
-            if (child.isMesh) {
-                child.material.specular.setScalar(0.1);
-                child.material.normalMap = normal;
-            }
-        });
-
-        scene.add(object);
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    controls = new TrackballControls(camera, renderer.domElement);
-
-    window.addEventListener('resize', resize);
-}
-
-function resize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_3mf.ts b/examples-testing/examples/webgl_loader_3mf.ts
deleted file mode 100644
index c31e32196..000000000
--- a/examples-testing/examples/webgl_loader_3mf.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, object, loader, controls;
-
-const params = {
-    asset: 'cube_gears',
-};
-
-const assets = ['cube_gears', 'facecolors', 'multipletextures', 'vertexcolors'];
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x333333);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
-
-    // Z is up for objects intended to be 3D printed.
-
-    camera.up.set(0, 0, 1);
-    camera.position.set(-100, -250, 100);
-    scene.add(camera);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 50;
-    controls.maxDistance = 400;
-    controls.enablePan = false;
-    controls.update();
-
-    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
-
-    const light = new THREE.DirectionalLight(0xffffff, 2);
-    light.position.set(-1, -2.5, 1);
-    scene.add(light);
-
-    const manager = new THREE.LoadingManager();
-
-    manager.onLoad = function () {
-        const aabb = new THREE.Box3().setFromObject(object);
-        const center = aabb.getCenter(new THREE.Vector3());
-
-        object.position.x += object.position.x - center.x;
-        object.position.y += object.position.y - center.y;
-        object.position.z += object.position.z - center.z;
-
-        controls.reset();
-
-        scene.add(object);
-        render();
-    };
-
-    loader = new ThreeMFLoader(manager);
-    loadAsset(params.asset);
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'asset', assets).onChange(function (value) {
-        loadAsset(value);
-    });
-}
-
-function loadAsset(asset) {
-    loader.load('models/3mf/' + asset + '.3mf', function (group) {
-        if (object) {
-            object.traverse(function (child) {
-                if (child.material) child.material.dispose();
-                if (child.material && child.material.map) child.material.map.dispose();
-                if (child.geometry) child.geometry.dispose();
-            });
-
-            scene.remove(object);
-        }
-
-        //
-
-        object = group;
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_3mf_materials.ts b/examples-testing/examples/webgl_loader_3mf_materials.ts
deleted file mode 100644
index fcdd7308e..000000000
--- a/examples-testing/examples/webgl_loader_3mf_materials.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 10, 500);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.set(-50, 40, 50);
-    scene.add(camera);
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
-    hemiLight.position.set(0, 100, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(-0, 40, 50);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.top = 50;
-    dirLight.shadow.camera.bottom = -25;
-    dirLight.shadow.camera.left = -25;
-    dirLight.shadow.camera.right = 25;
-    dirLight.shadow.camera.near = 0.1;
-    dirLight.shadow.camera.far = 200;
-    dirLight.shadow.mapSize.set(1024, 1024);
-    scene.add(dirLight);
-
-    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
-
-    //
-
-    const manager = new THREE.LoadingManager();
-
-    const loader = new ThreeMFLoader(manager);
-    loader.load('./models/3mf/truck.3mf', function (object) {
-        object.rotation.set(-Math.PI / 2, 0, 0); // z-up conversion
-
-        object.traverse(function (child) {
-            child.castShadow = true;
-        });
-
-        scene.add(object);
-    });
-
-    //
-
-    manager.onLoad = function () {
-        render();
-    };
-
-    //
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(1000, 1000),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
-    );
-    ground.rotation.x = -Math.PI / 2;
-    ground.position.y = 11;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 50;
-    controls.maxDistance = 200;
-    controls.enablePan = false;
-    controls.target.set(0, 20, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-
-    render();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_amf.ts b/examples-testing/examples/webgl_loader_amf.ts
deleted file mode 100644
index ee576e04f..000000000
--- a/examples-testing/examples/webgl_loader_amf.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x999999);
-
-    scene.add(new THREE.AmbientLight(0x999999));
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
-
-    // Z is up for objects intended to be 3D printed.
-
-    camera.up.set(0, 0, 1);
-    camera.position.set(0, -9, 6);
-
-    camera.add(new THREE.PointLight(0xffffff, 250));
-
-    scene.add(camera);
-
-    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x555555);
-    grid.rotateOnAxis(new THREE.Vector3(1, 0, 0), 90 * (Math.PI / 180));
-    scene.add(grid);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const loader = new AMFLoader();
-    loader.load('./models/amf/rook.amf', function (amfobject) {
-        scene.add(amfobject);
-        render();
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.target.set(0, 0, 2);
-    controls.enableZoom = false;
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_bvh.ts b/examples-testing/examples/webgl_loader_bvh.ts
deleted file mode 100644
index 0be3add4d..000000000
--- a/examples-testing/examples/webgl_loader_bvh.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';
-
-const clock = new THREE.Clock();
-
-let camera, controls, scene, renderer;
-let mixer;
-
-init();
-
-const loader = new BVHLoader();
-loader.load('models/bvh/pirouette.bvh', function (result) {
-    const skeletonHelper = new THREE.SkeletonHelper(result.skeleton.bones[0]);
-
-    scene.add(result.skeleton.bones[0]);
-    scene.add(skeletonHelper);
-
-    // play animation
-    mixer = new THREE.AnimationMixer(result.skeleton.bones[0]);
-    mixer.clipAction(result.clip).play();
-});
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 200, 300);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xeeeeee);
-
-    scene.add(new THREE.GridHelper(400, 10));
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 300;
-    controls.maxDistance = 700;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) mixer.update(delta);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_collada.ts b/examples-testing/examples/webgl_loader_collada.ts
deleted file mode 100644
index 62588b698..000000000
--- a/examples-testing/examples/webgl_loader_collada.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';
-
-let container, stats, clock;
-let camera, scene, renderer, elf;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
-    camera.position.set(8, 10, 8);
-    camera.lookAt(0, 3, 0);
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    // loading manager
-
-    const loadingManager = new THREE.LoadingManager(function () {
-        scene.add(elf);
-    });
-
-    // collada
-
-    const loader = new ColladaLoader(loadingManager);
-    loader.load('./models/collada/elf/elf.dae', function (collada) {
-        elf = collada.scene;
-    });
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
-    directionalLight.position.set(1, 1, 0).normalize();
-    scene.add(directionalLight);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    if (elf !== undefined) {
-        elf.rotation.z += delta * 0.5;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_collada_skinning.ts b/examples-testing/examples/webgl_loader_collada_skinning.ts
deleted file mode 100644
index 5cb808b17..000000000
--- a/examples-testing/examples/webgl_loader_collada_skinning.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, stats, clock, controls;
-let camera, scene, renderer, mixer;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(15, 10, -15);
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    // collada
-
-    const loader = new ColladaLoader();
-    loader.load('./models/collada/stormtrooper/stormtrooper.dae', function (collada) {
-        const avatar = collada.scene;
-        const animations = avatar.animations;
-
-        mixer = new THREE.AnimationMixer(avatar);
-        mixer.clipAction(animations[0]).play();
-
-        scene.add(avatar);
-    });
-
-    //
-
-    const gridHelper = new THREE.GridHelper(10, 20, 0xc1c1c1, 0x8d8d8d);
-    scene.add(gridHelper);
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(1.5, 1, -1.5);
-    scene.add(directionalLight);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.screenSpacePanning = true;
-    controls.minDistance = 5;
-    controls.maxDistance = 40;
-    controls.target.set(0, 2, 0);
-    controls.update();
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    if (mixer !== undefined) {
-        mixer.update(delta);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_draco.ts b/examples-testing/examples/webgl_loader_draco.ts
deleted file mode 100644
index c9947c693..000000000
--- a/examples-testing/examples/webgl_loader_draco.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as THREE from 'three';
-
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-let camera, scene, renderer;
-
-const container = document.querySelector('#container');
-
-// Configure and create Draco decoder.
-const dracoLoader = new DRACOLoader();
-dracoLoader.setDecoderPath('jsm/libs/draco/');
-dracoLoader.setDecoderConfig({ type: 'js' });
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);
-    camera.position.set(3, 0.25, 3);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x443333);
-    scene.fog = new THREE.Fog(0x443333, 1, 4);
-
-    // Ground
-    const plane = new THREE.Mesh(
-        new THREE.PlaneGeometry(8, 8),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, specular: 0x101010 }),
-    );
-    plane.rotation.x = -Math.PI / 2;
-    plane.position.y = 0.03;
-    plane.receiveShadow = true;
-    scene.add(plane);
-
-    // Lights
-    const hemiLight = new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3);
-    scene.add(hemiLight);
-
-    const spotLight = new THREE.SpotLight();
-    spotLight.intensity = 7;
-    spotLight.angle = Math.PI / 16;
-    spotLight.penumbra = 0.5;
-    spotLight.castShadow = true;
-    spotLight.position.set(-1, 1, 1);
-    scene.add(spotLight);
-
-    dracoLoader.load('models/draco/bunny.drc', function (geometry) {
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshStandardMaterial({ color: 0xa5a5a5 });
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-        scene.add(mesh);
-
-        // Release decoder resources.
-        dracoLoader.dispose();
-    });
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const timer = Date.now() * 0.0003;
-
-    camera.position.x = Math.sin(timer) * 0.5;
-    camera.position.z = Math.cos(timer) * 0.5;
-    camera.lookAt(0, 0.1, 0);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_fbx.ts b/examples-testing/examples/webgl_loader_fbx.ts
deleted file mode 100644
index 3b157a222..000000000
--- a/examples-testing/examples/webgl_loader_fbx.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const manager = new THREE.LoadingManager();
-
-let camera, scene, renderer, stats, object, loader, guiMorphsFolder;
-let mixer;
-
-const clock = new THREE.Clock();
-
-const params = {
-    asset: 'Samba Dancing',
-};
-
-const assets = ['Samba Dancing', 'morph_test'];
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(100, 200, 300);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-    scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 5);
-    hemiLight.position.set(0, 200, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 5);
-    dirLight.position.set(0, 200, 100);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.top = 180;
-    dirLight.shadow.camera.bottom = -100;
-    dirLight.shadow.camera.left = -120;
-    dirLight.shadow.camera.right = 120;
-    scene.add(dirLight);
-
-    // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
-
-    // ground
-    const mesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(2000, 2000),
-        new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }),
-    );
-    mesh.rotation.x = -Math.PI / 2;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    const grid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
-    grid.material.opacity = 0.2;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    loader = new FBXLoader(manager);
-    loadAsset(params.asset);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 100, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-
-    // stats
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const gui = new GUI();
-    gui.add(params, 'asset', assets).onChange(function (value) {
-        loadAsset(value);
-    });
-
-    guiMorphsFolder = gui.addFolder('Morphs').hide();
-}
-
-function loadAsset(asset) {
-    loader.load('models/fbx/' + asset + '.fbx', function (group) {
-        if (object) {
-            object.traverse(function (child) {
-                if (child.material) {
-                    const materials = Array.isArray(child.material) ? child.material : [child.material];
-                    materials.forEach(material => {
-                        if (material.map) material.map.dispose();
-                        material.dispose();
-                    });
-                }
-
-                if (child.geometry) child.geometry.dispose();
-            });
-
-            scene.remove(object);
-        }
-
-        //
-
-        object = group;
-
-        if (object.animations && object.animations.length) {
-            mixer = new THREE.AnimationMixer(object);
-
-            const action = mixer.clipAction(object.animations[0]);
-            action.play();
-        } else {
-            mixer = null;
-        }
-
-        guiMorphsFolder.children.forEach(child => child.destroy());
-        guiMorphsFolder.hide();
-
-        object.traverse(function (child) {
-            if (child.isMesh) {
-                child.castShadow = true;
-                child.receiveShadow = true;
-
-                if (child.morphTargetDictionary) {
-                    guiMorphsFolder.show();
-                    const meshFolder = guiMorphsFolder.addFolder(child.name || child.uuid);
-                    Object.keys(child.morphTargetDictionary).forEach(key => {
-                        meshFolder.add(child.morphTargetInfluences, child.morphTargetDictionary[key], 0, 1, 0.01);
-                    });
-                }
-            }
-        });
-
-        scene.add(object);
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) mixer.update(delta);
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_loader_fbx_nurbs.ts b/examples-testing/examples/webgl_loader_fbx_nurbs.ts
deleted file mode 100644
index f2e45bcb5..000000000
--- a/examples-testing/examples/webgl_loader_fbx_nurbs.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(2, 18, 28);
-
-    scene = new THREE.Scene();
-
-    // grid
-    const gridHelper = new THREE.GridHelper(28, 28, 0x303030, 0x303030);
-    scene.add(gridHelper);
-
-    // stats
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // model
-    const loader = new FBXLoader();
-    loader.load('models/fbx/nurbs.fbx', function (object) {
-        scene.add(object);
-    });
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 12, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_loader_gcode.ts b/examples-testing/examples/webgl_loader_gcode.ts
deleted file mode 100644
index 6fd3e149d..000000000
--- a/examples-testing/examples/webgl_loader_gcode.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 70);
-
-    scene = new THREE.Scene();
-
-    const loader = new GCodeLoader();
-    loader.load('models/gcode/benchy.gcode', function (object) {
-        object.position.set(-100, -20, 100);
-        scene.add(object);
-
-        render();
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 10;
-    controls.maxDistance = 100;
-
-    window.addEventListener('resize', resize);
-}
-
-function resize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf.ts b/examples-testing/examples/webgl_loader_gltf.ts
deleted file mode 100644
index e1b0adc51..000000000
--- a/examples-testing/examples/webgl_loader_gltf.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        render();
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', async function (gltf) {
-            const model = gltf.scene;
-
-            // wait until the model can be added to the scene without blocking due to shader compilation
-
-            await renderer.compileAsync(model, camera, scene);
-
-            scene.add(model);
-
-            render();
-        });
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_anisotropy.ts b/examples-testing/examples/webgl_loader_gltf_anisotropy.ts
deleted file mode 100644
index 6e240a272..000000000
--- a/examples-testing/examples/webgl_loader_gltf_anisotropy.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let renderer, scene, camera, controls;
-
-init();
-
-async function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1.35;
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.set(-0.35, -0.2, 0.35);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, -0.08, 0.11);
-    controls.minDistance = 0.1;
-    controls.maxDistance = 2;
-    controls.addEventListener('change', render);
-    controls.update();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
-
-    const [texture, gltf] = await Promise.all([
-        rgbeLoader.loadAsync('royal_esplanade_1k.hdr'),
-        gltfLoader.loadAsync('AnisotropyBarnLamp.glb'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.backgroundBlurriness = 0.5;
-    scene.environment = texture;
-
-    // model
-
-    scene.add(gltf.scene);
-
-    render();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_avif.ts b/examples-testing/examples/webgl_loader_gltf_avif.ts
deleted file mode 100644
index 37d63859e..000000000
--- a/examples-testing/examples/webgl_loader_gltf_avif.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(1.5, 4, 9);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf6eedc);
-
-    //
-
-    const dracoLoader = new DRACOLoader();
-    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
-
-    const loader = new GLTFLoader();
-    loader.setDRACOLoader(dracoLoader);
-    loader.setPath('models/gltf/AVIFTest/');
-    loader.load('forest_house.glb', function (gltf) {
-        scene.add(gltf.scene);
-
-        render();
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.target.set(0, 2, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_compressed.ts b/examples-testing/examples/webgl_loader_gltf_compressed.ts
deleted file mode 100644
index 3bdcea8ec..000000000
--- a/examples-testing/examples/webgl_loader_gltf_compressed.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as THREE from 'three';
-
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(0, 100, 0);
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xbbbbbb);
-    scene.environment = pmremGenerator.fromScene(environment).texture;
-    environment.dispose();
-
-    const grid = new THREE.GridHelper(500, 10, 0xffffff, 0xffffff);
-    grid.material.opacity = 0.5;
-    grid.material.depthWrite = false;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
-
-    const loader = new GLTFLoader().setPath('models/gltf/');
-    loader.setKTX2Loader(ktx2Loader);
-    loader.setMeshoptDecoder(MeshoptDecoder);
-    loader.load('coffeemat.glb', function (gltf) {
-        // coffeemat.glb was produced from the source scene using gltfpack:
-        // gltfpack -i coffeemat/scene.gltf -o coffeemat.glb -cc -tc
-        // The resulting model uses EXT_meshopt_compression (for geometry) and KHR_texture_basisu (for texture compression using ETC1S/BasisLZ)
-
-        gltf.scene.position.y = 8;
-
-        scene.add(gltf.scene);
-
-        render();
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 400;
-    controls.maxDistance = 1000;
-    controls.target.set(10, 90, -16);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_dispersion.ts b/examples-testing/examples/webgl_loader_gltf_dispersion.ts
deleted file mode 100644
index 100badcab..000000000
--- a/examples-testing/examples/webgl_loader_gltf_dispersion.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-let camera, scene, renderer;
-
-init().then(render);
-
-async function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 5);
-    camera.position.set(0.1, 0.05, 0.15);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.NeutralToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene = new THREE.Scene();
-    scene.backgroundBlurriness = 0.5;
-
-    const env = pmremGenerator.fromScene(environment).texture;
-    scene.background = env;
-    scene.environment = env;
-    environment.dispose();
-
-    const loader = new GLTFLoader();
-    const gltf = await loader.loadAsync('models/gltf/DispersionTest.glb');
-
-    scene.add(gltf.scene);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 0.1;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_instancing.ts b/examples-testing/examples/webgl_loader_gltf_instancing.ts
deleted file mode 100644
index 5d23e7750..000000000
--- a/examples-testing/examples/webgl_loader_gltf_instancing.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-0.9, 0.41, -0.89);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        render();
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF-instancing/');
-        loader.load('DamagedHelmetGpuInstancing.gltf', function (gltf) {
-            scene.add(gltf.scene);
-
-            render();
-        });
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 0.2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0.25, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_iridescence.ts b/examples-testing/examples/webgl_loader_gltf_iridescence.ts
deleted file mode 100644
index eb0f8d914..000000000
--- a/examples-testing/examples/webgl_loader_gltf_iridescence.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let renderer, scene, camera, controls;
-
-init().catch(function (err) {
-    console.error(err);
-});
-
-async function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setAnimationLoop(animate);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 20);
-    camera.position.set(0.35, 0.05, 0.35);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -0.5;
-    controls.target.set(0, 0.2, 0);
-    controls.update();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
-
-    const [texture, gltf] = await Promise.all([
-        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
-        gltfLoader.loadAsync('IridescenceLamp.glb'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.environment = texture;
-
-    // model
-
-    scene.add(gltf.scene);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_sheen.ts b/examples-testing/examples/webgl_loader_gltf_sheen.ts
deleted file mode 100644
index bd258d5c1..000000000
--- a/examples-testing/examples/webgl_loader_gltf_sheen.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, controls;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
-    camera.position.set(-0.75, 0.7, 1.25);
-
-    scene = new THREE.Scene();
-
-    // model
-
-    new GLTFLoader().setPath('models/gltf/').load('SheenChair.glb', function (gltf) {
-        scene.add(gltf.scene);
-
-        const object = gltf.scene.getObjectByName('SheenChair_fabric');
-
-        const gui = new GUI();
-
-        gui.add(object.material, 'sheen', 0, 1);
-        gui.open();
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene.background = new THREE.Color(0xbbbbbb);
-    scene.environment = pmremGenerator.fromScene(environment).texture;
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0.35, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    controls.update(); // required if damping enabled
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_gltf_transmission.ts b/examples-testing/examples/webgl_loader_gltf_transmission.ts
deleted file mode 100644
index 87a47d2c0..000000000
--- a/examples-testing/examples/webgl_loader_gltf_transmission.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-let camera, scene, renderer, controls, clock, mixer;
-
-init();
-
-function init() {
-    clock = new THREE.Clock();
-
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(0, 0.4, 0.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.backgroundBlurriness = 0.35;
-
-        scene.environment = texture;
-
-        // model
-
-        new GLTFLoader()
-            .setPath('models/gltf/')
-            .setDRACOLoader(new DRACOLoader().setDecoderPath('jsm/libs/draco/gltf/'))
-            .load('IridescentDishWithOlives.glb', function (gltf) {
-                mixer = new THREE.AnimationMixer(gltf.scene);
-                mixer.clipAction(gltf.animations[0]).play();
-
-                scene.add(gltf.scene);
-            });
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -0.75;
-    controls.enableDamping = true;
-    controls.minDistance = 0.5;
-    controls.maxDistance = 1;
-    controls.target.set(0, 0.1, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    if (mixer) mixer.update(clock.getDelta());
-
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_imagebitmap.ts b/examples-testing/examples/webgl_loader_imagebitmap.ts
deleted file mode 100644
index 1049e9857..000000000
--- a/examples-testing/examples/webgl_loader_imagebitmap.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let group, cubes;
-
-init();
-
-function addImageBitmap() {
-    new THREE.ImageBitmapLoader().load(
-        'textures/planets/earth_atmos_2048.jpg?' + performance.now(),
-        function (imageBitmap) {
-            const texture = new THREE.CanvasTexture(imageBitmap);
-            texture.colorSpace = THREE.SRGBColorSpace;
-            const material = new THREE.MeshBasicMaterial({ map: texture });
-
-            /* ImageBitmap should be disposed when done with it
-						   Can't be done until it's actually uploaded to WebGLTexture */
-
-            // imageBitmap.close();
-
-            addCube(material);
-        },
-        function (p) {
-            console.log(p);
-        },
-        function (e) {
-            console.log(e);
-        },
-    );
-}
-
-function addImage() {
-    new THREE.ImageLoader()
-        .setCrossOrigin('*')
-        .load('textures/planets/earth_atmos_2048.jpg?' + performance.now(), function (image) {
-            const texture = new THREE.CanvasTexture(image);
-            texture.colorSpace = THREE.SRGBColorSpace;
-            const material = new THREE.MeshBasicMaterial({ color: 0xff8888, map: texture });
-            addCube(material);
-        });
-}
-
-const geometry = new THREE.BoxGeometry();
-
-function addCube(material) {
-    const cube = new THREE.Mesh(geometry, material);
-    cube.position.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
-    cube.rotation.set(Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI);
-    cubes.add(cube);
-}
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
-    camera.position.set(0, 4, 7);
-    camera.lookAt(0, 0, 0);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    //
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    group.add(new THREE.GridHelper(4, 12, 0x888888, 0x444444));
-
-    cubes = new THREE.Group();
-    group.add(cubes);
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // TESTS
-
-    setTimeout(addImage, 300);
-    setTimeout(addImage, 600);
-    setTimeout(addImage, 900);
-    setTimeout(addImageBitmap, 1300);
-    setTimeout(addImageBitmap, 1600);
-    setTimeout(addImageBitmap, 1900);
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    group.rotation.y = performance.now() / 3000;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_kmz.ts b/examples-testing/examples/webgl_loader_kmz.ts
deleted file mode 100644
index f93555e41..000000000
--- a/examples-testing/examples/webgl_loader_kmz.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { KMZLoader } from 'three/addons/loaders/KMZLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x999999);
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0.5, 1.0, 0.5).normalize();
-
-    scene.add(light);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
-
-    camera.position.y = 5;
-    camera.position.z = 10;
-
-    scene.add(camera);
-
-    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x7b7b7b);
-    scene.add(grid);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const loader = new KMZLoader();
-    loader.load('./models/kmz/Box.kmz', function (kmz) {
-        kmz.scene.position.y = 0.5;
-        scene.add(kmz.scene);
-        render();
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_lwo.ts b/examples-testing/examples/webgl_loader_lwo.ts
deleted file mode 100644
index fb10c8340..000000000
--- a/examples-testing/examples/webgl_loader_lwo.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LWOLoader } from 'three/addons/loaders/LWOLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 200);
-    camera.position.set(0.7, 14.6, -43.2);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xa0a0a0);
-
-    const ambientLight = new THREE.AmbientLight(0xbbbbbb);
-    scene.add(ambientLight);
-
-    const light1 = new THREE.DirectionalLight(0xc1c1c1, 3);
-    light1.position.set(0, 200, -100);
-    scene.add(light1);
-
-    const grid = new THREE.GridHelper(200, 20, 0x000000, 0x000000);
-    grid.material.opacity = 0.3;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    const loader = new LWOLoader();
-    loader.load('models/lwo/Objects/LWO3/Demo.lwo', function (object) {
-        const phong = object.meshes[0];
-        phong.position.set(2, 12, 0);
-
-        const standard = object.meshes[1];
-        standard.position.set(-2, 12, 0);
-
-        const rocket = object.meshes[2];
-        rocket.position.set(0, 10.5, 1);
-
-        scene.add(phong, standard, rocket);
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(-1.33, 10, 6.7);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_md2_control.ts b/examples-testing/examples/webgl_loader_md2_control.ts
deleted file mode 100644
index 683e4c2ad..000000000
--- a/examples-testing/examples/webgl_loader_md2_control.ts
+++ /dev/null
@@ -1,289 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { MD2CharacterComplex } from 'three/addons/misc/MD2CharacterComplex.js';
-import { Gyroscope } from 'three/addons/misc/Gyroscope.js';
-
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-
-let container, stats;
-let camera, scene, renderer;
-
-const characters = [];
-let nCharacters = 0;
-
-let cameraControls;
-
-const controls = {
-    moveForward: false,
-    moveBackward: false,
-    moveLeft: false,
-    moveRight: false,
-};
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
-    camera.position.set(0, 150, 1300);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-    scene.fog = new THREE.Fog(0xffffff, 1000, 4000);
-
-    scene.add(camera);
-
-    // LIGHTS
-
-    scene.add(new THREE.AmbientLight(0x666666, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 7);
-    light.position.set(200, 450, 500);
-
-    light.castShadow = true;
-
-    light.shadow.mapSize.width = 1024;
-    light.shadow.mapSize.height = 512;
-
-    light.shadow.camera.near = 100;
-    light.shadow.camera.far = 1200;
-
-    light.shadow.camera.left = -1000;
-    light.shadow.camera.right = 1000;
-    light.shadow.camera.top = 350;
-    light.shadow.camera.bottom = -350;
-
-    scene.add(light);
-    // scene.add( new THREE.CameraHelper( light.shadow.camera ) );
-
-    //  GROUND
-
-    const gt = new THREE.TextureLoader().load('textures/terrain/grasslight-big.jpg');
-    const gg = new THREE.PlaneGeometry(16000, 16000);
-    const gm = new THREE.MeshPhongMaterial({ color: 0xffffff, map: gt });
-
-    const ground = new THREE.Mesh(gg, gm);
-    ground.rotation.x = -Math.PI / 2;
-    ground.material.map.repeat.set(64, 64);
-    ground.material.map.wrapS = THREE.RepeatWrapping;
-    ground.material.map.wrapT = THREE.RepeatWrapping;
-    ground.material.map.colorSpace = THREE.SRGBColorSpace;
-    // note that because the ground does not cast a shadow, .castShadow is left false
-    ground.receiveShadow = true;
-
-    scene.add(ground);
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('keydown', onKeyDown);
-    document.addEventListener('keyup', onKeyUp);
-
-    // CONTROLS
-
-    cameraControls = new OrbitControls(camera, renderer.domElement);
-    cameraControls.target.set(0, 50, 0);
-    cameraControls.update();
-
-    // CHARACTER
-
-    const configOgro = {
-        baseUrl: 'models/md2/ogro/',
-
-        body: 'ogro.md2',
-        skins: [
-            'grok.jpg',
-            'ogrobase.png',
-            'arboshak.png',
-            'ctf_r.png',
-            'ctf_b.png',
-            'darkam.png',
-            'freedom.png',
-            'gib.png',
-            'gordogh.png',
-            'igdosh.png',
-            'khorne.png',
-            'nabogro.png',
-            'sharokh.png',
-        ],
-        weapons: [['weapon.md2', 'weapon.jpg']],
-        animations: {
-            move: 'run',
-            idle: 'stand',
-            jump: 'jump',
-            attack: 'attack',
-            crouchMove: 'cwalk',
-            crouchIdle: 'cstand',
-            crouchAttach: 'crattack',
-        },
-
-        walkSpeed: 350,
-        crouchSpeed: 175,
-    };
-
-    const nRows = 1;
-    const nSkins = configOgro.skins.length;
-
-    nCharacters = nSkins * nRows;
-
-    for (let i = 0; i < nCharacters; i++) {
-        const character = new MD2CharacterComplex();
-        character.scale = 3;
-        character.controls = controls;
-        characters.push(character);
-    }
-
-    const baseCharacter = new MD2CharacterComplex();
-    baseCharacter.scale = 3;
-
-    baseCharacter.onLoadComplete = function () {
-        let k = 0;
-
-        for (let j = 0; j < nRows; j++) {
-            for (let i = 0; i < nSkins; i++) {
-                const cloneCharacter = characters[k];
-
-                cloneCharacter.shareParts(baseCharacter);
-
-                // cast and receive shadows
-                cloneCharacter.enableShadows(true);
-
-                cloneCharacter.setWeapon(0);
-                cloneCharacter.setSkin(i);
-
-                cloneCharacter.root.position.x = (i - nSkins / 2) * 150;
-                cloneCharacter.root.position.z = j * 250;
-
-                scene.add(cloneCharacter.root);
-
-                k++;
-            }
-        }
-
-        const gyro = new Gyroscope();
-        gyro.add(camera);
-        gyro.add(light, light.target);
-
-        characters[Math.floor(nSkins / 2)].root.add(gyro);
-    };
-
-    baseCharacter.loadParts(configOgro);
-}
-
-// EVENT HANDLERS
-
-function onWindowResize() {
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-}
-
-function onKeyDown(event) {
-    switch (event.code) {
-        case 'ArrowUp':
-        case 'KeyW':
-            controls.moveForward = true;
-            break;
-
-        case 'ArrowDown':
-        case 'KeyS':
-            controls.moveBackward = true;
-            break;
-
-        case 'ArrowLeft':
-        case 'KeyA':
-            controls.moveLeft = true;
-            break;
-
-        case 'ArrowRight':
-        case 'KeyD':
-            controls.moveRight = true;
-            break;
-
-        // case 'KeyC': controls.crouch = true; break;
-        // case 'Space': controls.jump = true; break;
-        // case 'ControlLeft':
-        // case 'ControlRight': controls.attack = true; break;
-    }
-}
-
-function onKeyUp(event) {
-    switch (event.code) {
-        case 'ArrowUp':
-        case 'KeyW':
-            controls.moveForward = false;
-            break;
-
-        case 'ArrowDown':
-        case 'KeyS':
-            controls.moveBackward = false;
-            break;
-
-        case 'ArrowLeft':
-        case 'KeyA':
-            controls.moveLeft = false;
-            break;
-
-        case 'ArrowRight':
-        case 'KeyD':
-            controls.moveRight = false;
-            break;
-
-        // case 'KeyC': controls.crouch = false; break;
-        // case 'Space': controls.jump = false; break;
-        // case 'ControlLeft':
-        // case 'ControlRight': controls.attack = false; break;
-    }
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    for (let i = 0; i < nCharacters; i++) {
-        characters[i].update(delta);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_mdd.ts b/examples-testing/examples/webgl_loader_mdd.ts
deleted file mode 100644
index 5b13e8f4b..000000000
--- a/examples-testing/examples/webgl_loader_mdd.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as THREE from 'three';
-
-import { MDDLoader } from 'three/addons/loaders/MDDLoader.js';
-
-let camera, scene, renderer, mixer, clock;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(8, 8, 8);
-    camera.lookAt(scene.position);
-
-    clock = new THREE.Clock();
-
-    //
-
-    const loader = new MDDLoader();
-    loader.load('models/mdd/cube.mdd', function (result) {
-        const morphTargets = result.morphTargets;
-        const clip = result.clip;
-        // clip.optimize(); // optional
-
-        const geometry = new THREE.BoxGeometry();
-        geometry.morphAttributes.position = morphTargets; // apply morph targets
-
-        const material = new THREE.MeshNormalMaterial();
-
-        const mesh = new THREE.Mesh(geometry, material);
-        scene.add(mesh);
-
-        mixer = new THREE.AnimationMixer(mesh);
-        mixer.clipAction(clip).play(); // use clip
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) mixer.update(delta);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_obj.ts b/examples-testing/examples/webgl_loader_obj.ts
deleted file mode 100644
index f61eeb758..000000000
--- a/examples-testing/examples/webgl_loader_obj.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import * as THREE from 'three';
-
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-let object;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
-    camera.position.z = 2.5;
-
-    // scene
-
-    scene = new THREE.Scene();
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 15);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    // manager
-
-    function loadModel() {
-        object.traverse(function (child) {
-            if (child.isMesh) child.material.map = texture;
-        });
-
-        object.position.y = -0.95;
-        object.scale.setScalar(0.01);
-        scene.add(object);
-
-        render();
-    }
-
-    const manager = new THREE.LoadingManager(loadModel);
-
-    // texture
-
-    const textureLoader = new THREE.TextureLoader(manager);
-    const texture = textureLoader.load('textures/uv_grid_opengl.jpg', render);
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    // model
-
-    function onProgress(xhr) {
-        if (xhr.lengthComputable) {
-            const percentComplete = (xhr.loaded / xhr.total) * 100;
-            console.log('model ' + percentComplete.toFixed(2) + '% downloaded');
-        }
-    }
-
-    function onError() {}
-
-    const loader = new OBJLoader(manager);
-    loader.load(
-        'models/obj/male02/male02.obj',
-        function (obj) {
-            object = obj;
-        },
-        onProgress,
-        onError,
-    );
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 5;
-    controls.addEventListener('change', render);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_obj_mtl.ts b/examples-testing/examples/webgl_loader_obj_mtl.ts
deleted file mode 100644
index 4308aee7b..000000000
--- a/examples-testing/examples/webgl_loader_obj_mtl.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import * as THREE from 'three';
-
-import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
-    camera.position.z = 2.5;
-
-    // scene
-
-    scene = new THREE.Scene();
-
-    const ambientLight = new THREE.AmbientLight(0xffffff);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 15);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    // model
-
-    const onProgress = function (xhr) {
-        if (xhr.lengthComputable) {
-            const percentComplete = (xhr.loaded / xhr.total) * 100;
-            console.log(percentComplete.toFixed(2) + '% downloaded');
-        }
-    };
-
-    new MTLLoader().setPath('models/obj/male02/').load('male02.mtl', function (materials) {
-        materials.preload();
-
-        new OBJLoader()
-            .setMaterials(materials)
-            .setPath('models/obj/male02/')
-            .load(
-                'male02.obj',
-                function (object) {
-                    object.position.y = -0.95;
-                    object.scale.setScalar(0.01);
-                    scene.add(object);
-                },
-                onProgress,
-            );
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 5;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_pcd.ts b/examples-testing/examples/webgl_loader_pcd.ts
deleted file mode 100644
index d69e3fa2a..000000000
--- a/examples-testing/examples/webgl_loader_pcd.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.01, 40);
-    camera.position.set(0, 0, 1);
-    scene.add(camera);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 0.5;
-    controls.maxDistance = 10;
-
-    //scene.add( new THREE.AxesHelper( 1 ) );
-
-    const loader = new PCDLoader();
-    loader.load('./models/pcd/binary/Zaghetto.pcd', function (points) {
-        points.geometry.center();
-        points.geometry.rotateX(Math.PI);
-        points.name = 'Zaghetto.pcd';
-        scene.add(points);
-
-        //
-
-        const gui = new GUI();
-
-        gui.add(points.material, 'size', 0.001, 0.01).onChange(render);
-        gui.addColor(points.material, 'color').onChange(render);
-        gui.open();
-
-        //
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_pdb.ts b/examples-testing/examples/webgl_loader_pdb.ts
deleted file mode 100644
index b560efa73..000000000
--- a/examples-testing/examples/webgl_loader_pdb.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import * as THREE from 'three';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';
-import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, labelRenderer;
-let controls;
-
-let root;
-
-const MOLECULES = {
-    Ethanol: 'ethanol.pdb',
-    Aspirin: 'aspirin.pdb',
-    Caffeine: 'caffeine.pdb',
-    Nicotine: 'nicotine.pdb',
-    LSD: 'lsd.pdb',
-    Cocaine: 'cocaine.pdb',
-    Cholesterol: 'cholesterol.pdb',
-    Lycopene: 'lycopene.pdb',
-    Glucose: 'glucose.pdb',
-    'Aluminium oxide': 'Al2O3.pdb',
-    Cubane: 'cubane.pdb',
-    Copper: 'cu.pdb',
-    Fluorite: 'caf2.pdb',
-    Salt: 'nacl.pdb',
-    'YBCO superconductor': 'ybco.pdb',
-    Buckyball: 'buckyball.pdb',
-    Graphite: 'graphite.pdb',
-};
-
-const params = {
-    molecule: 'caffeine.pdb',
-};
-
-const loader = new PDBLoader();
-const offset = new THREE.Vector3();
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.z = 1000;
-    scene.add(camera);
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 2.5);
-    light1.position.set(1, 1, 1);
-    scene.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 1.5);
-    light2.position.set(-1, -1, 1);
-    scene.add(light2);
-
-    root = new THREE.Group();
-    scene.add(root);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.getElementById('container').appendChild(renderer.domElement);
-
-    labelRenderer = new CSS2DRenderer();
-    labelRenderer.setSize(window.innerWidth, window.innerHeight);
-    labelRenderer.domElement.style.position = 'absolute';
-    labelRenderer.domElement.style.top = '0px';
-    labelRenderer.domElement.style.pointerEvents = 'none';
-    document.getElementById('container').appendChild(labelRenderer.domElement);
-
-    //
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.minDistance = 500;
-    controls.maxDistance = 2000;
-
-    //
-
-    loadMolecule(params.molecule);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'molecule', MOLECULES).onChange(loadMolecule);
-    gui.open();
-}
-
-//
-
-function loadMolecule(model) {
-    const url = 'models/pdb/' + model;
-
-    while (root.children.length > 0) {
-        const object = root.children[0];
-        object.parent.remove(object);
-    }
-
-    loader.load(url, function (pdb) {
-        const geometryAtoms = pdb.geometryAtoms;
-        const geometryBonds = pdb.geometryBonds;
-        const json = pdb.json;
-
-        const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
-        const sphereGeometry = new THREE.IcosahedronGeometry(1, 3);
-
-        geometryAtoms.computeBoundingBox();
-        geometryAtoms.boundingBox.getCenter(offset).negate();
-
-        geometryAtoms.translate(offset.x, offset.y, offset.z);
-        geometryBonds.translate(offset.x, offset.y, offset.z);
-
-        let positions = geometryAtoms.getAttribute('position');
-        const colors = geometryAtoms.getAttribute('color');
-
-        const position = new THREE.Vector3();
-        const color = new THREE.Color();
-
-        for (let i = 0; i < positions.count; i++) {
-            position.x = positions.getX(i);
-            position.y = positions.getY(i);
-            position.z = positions.getZ(i);
-
-            color.r = colors.getX(i);
-            color.g = colors.getY(i);
-            color.b = colors.getZ(i);
-
-            const material = new THREE.MeshPhongMaterial({ color: color });
-
-            const object = new THREE.Mesh(sphereGeometry, material);
-            object.position.copy(position);
-            object.position.multiplyScalar(75);
-            object.scale.multiplyScalar(25);
-            root.add(object);
-
-            const atom = json.atoms[i];
-
-            const text = document.createElement('div');
-            text.className = 'label';
-            text.style.color = 'rgb(' + atom[3][0] + ',' + atom[3][1] + ',' + atom[3][2] + ')';
-            text.textContent = atom[4];
-
-            const label = new CSS2DObject(text);
-            label.position.copy(object.position);
-            root.add(label);
-        }
-
-        positions = geometryBonds.getAttribute('position');
-
-        const start = new THREE.Vector3();
-        const end = new THREE.Vector3();
-
-        for (let i = 0; i < positions.count; i += 2) {
-            start.x = positions.getX(i);
-            start.y = positions.getY(i);
-            start.z = positions.getZ(i);
-
-            end.x = positions.getX(i + 1);
-            end.y = positions.getY(i + 1);
-            end.z = positions.getZ(i + 1);
-
-            start.multiplyScalar(75);
-            end.multiplyScalar(75);
-
-            const object = new THREE.Mesh(boxGeometry, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-            object.position.copy(start);
-            object.position.lerp(end, 0.5);
-            object.scale.set(5, 5, start.distanceTo(end));
-            object.lookAt(end);
-            root.add(object);
-        }
-    });
-}
-
-//
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    labelRenderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-
-    const time = Date.now() * 0.0004;
-
-    root.rotation.x = time;
-    root.rotation.y = time * 0.7;
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-    labelRenderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_ply.ts b/examples-testing/examples/webgl_loader_ply.ts
deleted file mode 100644
index 0f4042b7d..000000000
--- a/examples-testing/examples/webgl_loader_ply.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
-
-let container, stats;
-
-let camera, cameraTarget, scene, renderer;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15);
-    camera.position.set(3, 0.15, 3);
-
-    cameraTarget = new THREE.Vector3(0, -0.1, 0);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x72645b);
-    scene.fog = new THREE.Fog(0x72645b, 2, 15);
-
-    // Ground
-
-    const plane = new THREE.Mesh(
-        new THREE.PlaneGeometry(40, 40),
-        new THREE.MeshPhongMaterial({ color: 0xcbcbcb, specular: 0x474747 }),
-    );
-    plane.rotation.x = -Math.PI / 2;
-    plane.position.y = -0.5;
-    scene.add(plane);
-
-    plane.receiveShadow = true;
-
-    // PLY file
-
-    const loader = new PLYLoader();
-    loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshStandardMaterial({ color: 0x009cff, flatShading: true });
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.y = -0.2;
-        mesh.position.z = 0.3;
-        mesh.rotation.x = -Math.PI / 2;
-        mesh.scale.multiplyScalar(0.001);
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-    });
-
-    loader.load('./models/ply/binary/Lucy100k.ply', function (geometry) {
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshStandardMaterial({ color: 0x009cff, flatShading: true });
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = -0.2;
-        mesh.position.y = -0.02;
-        mesh.position.z = -0.2;
-        mesh.scale.multiplyScalar(0.0006);
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-    });
-
-    // Lights
-
-    scene.add(new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3));
-
-    addShadowedLight(1, 1, 1, 0xffffff, 3.5);
-    addShadowedLight(0.5, 1, -1, 0xffd500, 3);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    renderer.shadowMap.enabled = true;
-
-    container.appendChild(renderer.domElement);
-
-    // stats
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // resize
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function addShadowedLight(x, y, z, color, intensity) {
-    const directionalLight = new THREE.DirectionalLight(color, intensity);
-    directionalLight.position.set(x, y, z);
-    scene.add(directionalLight);
-
-    directionalLight.castShadow = true;
-
-    const d = 1;
-    directionalLight.shadow.camera.left = -d;
-    directionalLight.shadow.camera.right = d;
-    directionalLight.shadow.camera.top = d;
-    directionalLight.shadow.camera.bottom = -d;
-
-    directionalLight.shadow.camera.near = 1;
-    directionalLight.shadow.camera.far = 4;
-
-    directionalLight.shadow.mapSize.width = 1024;
-    directionalLight.shadow.mapSize.height = 1024;
-
-    directionalLight.shadow.bias = -0.001;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const timer = Date.now() * 0.0005;
-
-    camera.position.x = Math.sin(timer) * 2.5;
-    camera.position.z = Math.cos(timer) * 2.5;
-
-    camera.lookAt(cameraTarget);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_svg.ts b/examples-testing/examples/webgl_loader_svg.ts
deleted file mode 100644
index 45361b92f..000000000
--- a/examples-testing/examples/webgl_loader_svg.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-import * as THREE from 'three';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
-
-let renderer, scene, camera, gui, guiData;
-
-init();
-
-//
-
-function init() {
-    const container = document.getElementById('container');
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 200);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.screenSpacePanning = true;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    guiData = {
-        currentURL: 'models/svg/tiger.svg',
-        drawFillShapes: true,
-        drawStrokes: true,
-        fillShapesWireframe: false,
-        strokesWireframe: false,
-    };
-
-    loadSVG(guiData.currentURL);
-
-    createGUI();
-}
-
-function createGUI() {
-    if (gui) gui.destroy();
-
-    gui = new GUI();
-
-    gui.add(guiData, 'currentURL', {
-        Tiger: 'models/svg/tiger.svg',
-        'Joins and caps': 'models/svg/lineJoinsAndCaps.svg',
-        Hexagon: 'models/svg/hexagon.svg',
-        Energy: 'models/svg/energy.svg',
-        'Test 1': 'models/svg/tests/1.svg',
-        'Test 2': 'models/svg/tests/2.svg',
-        'Test 3': 'models/svg/tests/3.svg',
-        'Test 4': 'models/svg/tests/4.svg',
-        'Test 5': 'models/svg/tests/5.svg',
-        'Test 6': 'models/svg/tests/6.svg',
-        'Test 7': 'models/svg/tests/7.svg',
-        'Test 8': 'models/svg/tests/8.svg',
-        'Test 9': 'models/svg/tests/9.svg',
-        Units: 'models/svg/tests/units.svg',
-        Ordering: 'models/svg/tests/ordering.svg',
-        Defs: 'models/svg/tests/testDefs/Svg-defs.svg',
-        Defs2: 'models/svg/tests/testDefs/Svg-defs2.svg',
-        Defs3: 'models/svg/tests/testDefs/Wave-defs.svg',
-        Defs4: 'models/svg/tests/testDefs/defs4.svg',
-        Defs5: 'models/svg/tests/testDefs/defs5.svg',
-        'Style CSS inside defs': 'models/svg/style-css-inside-defs.svg',
-        'Multiple CSS classes': 'models/svg/multiple-css-classes.svg',
-        'Zero Radius': 'models/svg/zero-radius.svg',
-        'Styles in svg tag': 'models/svg/tests/styles.svg',
-        'Round join': 'models/svg/tests/roundJoinPrecisionIssue.svg',
-        'Ellipse Transformations': 'models/svg/tests/ellipseTransform.svg',
-        singlePointTest: 'models/svg/singlePointTest.svg',
-        singlePointTest2: 'models/svg/singlePointTest2.svg',
-        singlePointTest3: 'models/svg/singlePointTest3.svg',
-        emptyPath: 'models/svg/emptyPath.svg',
-    })
-        .name('SVG File')
-        .onChange(update);
-
-    gui.add(guiData, 'drawStrokes').name('Draw strokes').onChange(update);
-
-    gui.add(guiData, 'drawFillShapes').name('Draw fill shapes').onChange(update);
-
-    gui.add(guiData, 'strokesWireframe').name('Wireframe strokes').onChange(update);
-
-    gui.add(guiData, 'fillShapesWireframe').name('Wireframe fill shapes').onChange(update);
-
-    function update() {
-        loadSVG(guiData.currentURL);
-    }
-}
-
-function loadSVG(url) {
-    //
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xb0b0b0);
-
-    //
-
-    const helper = new THREE.GridHelper(160, 10, 0x8d8d8d, 0xc1c1c1);
-    helper.rotation.x = Math.PI / 2;
-    scene.add(helper);
-
-    //
-
-    const loader = new SVGLoader();
-
-    loader.load(url, function (data) {
-        const group = new THREE.Group();
-        group.scale.multiplyScalar(0.25);
-        group.position.x = -70;
-        group.position.y = 70;
-        group.scale.y *= -1;
-
-        let renderOrder = 0;
-
-        for (const path of data.paths) {
-            const fillColor = path.userData.style.fill;
-
-            if (guiData.drawFillShapes && fillColor !== undefined && fillColor !== 'none') {
-                const material = new THREE.MeshBasicMaterial({
-                    color: new THREE.Color().setStyle(fillColor),
-                    opacity: path.userData.style.fillOpacity,
-                    transparent: true,
-                    side: THREE.DoubleSide,
-                    depthWrite: false,
-                    wireframe: guiData.fillShapesWireframe,
-                });
-
-                const shapes = SVGLoader.createShapes(path);
-
-                for (const shape of shapes) {
-                    const geometry = new THREE.ShapeGeometry(shape);
-                    const mesh = new THREE.Mesh(geometry, material);
-                    mesh.renderOrder = renderOrder++;
-
-                    group.add(mesh);
-                }
-            }
-
-            const strokeColor = path.userData.style.stroke;
-
-            if (guiData.drawStrokes && strokeColor !== undefined && strokeColor !== 'none') {
-                const material = new THREE.MeshBasicMaterial({
-                    color: new THREE.Color().setStyle(strokeColor),
-                    opacity: path.userData.style.strokeOpacity,
-                    transparent: true,
-                    side: THREE.DoubleSide,
-                    depthWrite: false,
-                    wireframe: guiData.strokesWireframe,
-                });
-
-                for (const subPath of path.subPaths) {
-                    const geometry = SVGLoader.pointsToStroke(subPath.getPoints(), path.userData.style);
-
-                    if (geometry) {
-                        const mesh = new THREE.Mesh(geometry, material);
-                        mesh.renderOrder = renderOrder++;
-
-                        group.add(mesh);
-                    }
-                }
-            }
-        }
-
-        scene.add(group);
-
-        render();
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_texture_dds.ts b/examples-testing/examples/webgl_loader_texture_dds.ts
deleted file mode 100644
index bc4bd0572..000000000
--- a/examples-testing/examples/webgl_loader_texture_dds.ts
+++ /dev/null
@@ -1,207 +0,0 @@
-import * as THREE from 'three';
-
-import { DDSLoader } from 'three/addons/loaders/DDSLoader.js';
-
-let camera, scene, renderer;
-const meshes = [];
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 15;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.BoxGeometry(2, 2, 2);
-
-    /*
-				This is how compressed textures are supposed to be used:
-
-				DXT1 - RGB - opaque textures
-				DXT3 - RGBA - transparent textures with sharp alpha transitions
-				DXT5 - RGBA - transparent textures with full alpha range
-				*/
-
-    const loader = new DDSLoader();
-
-    const map1 = loader.load('textures/compressed/disturb_dxt1_nomip.dds');
-    map1.minFilter = map1.magFilter = THREE.LinearFilter;
-    map1.anisotropy = 4;
-    map1.colorSpace = THREE.SRGBColorSpace;
-
-    const map2 = loader.load('textures/compressed/disturb_dxt1_mip.dds');
-    map2.anisotropy = 4;
-    map2.colorSpace = THREE.SRGBColorSpace;
-
-    const map3 = loader.load('textures/compressed/hepatica_dxt3_mip.dds');
-    map3.anisotropy = 4;
-    map3.colorSpace = THREE.SRGBColorSpace;
-
-    const map4 = loader.load('textures/compressed/explosion_dxt5_mip.dds');
-    map4.anisotropy = 4;
-    map4.colorSpace = THREE.SRGBColorSpace;
-
-    const map5 = loader.load('textures/compressed/disturb_argb_nomip.dds');
-    map5.minFilter = map5.magFilter = THREE.LinearFilter;
-    map5.anisotropy = 4;
-    map5.colorSpace = THREE.SRGBColorSpace;
-
-    const map6 = loader.load('textures/compressed/disturb_argb_mip.dds');
-    map6.anisotropy = 4;
-    map6.colorSpace = THREE.SRGBColorSpace;
-
-    const map7 = loader.load('textures/compressed/disturb_dx10_bc6h_signed_nomip.dds');
-    map7.anisotropy = 4;
-
-    const map8 = loader.load('textures/compressed/disturb_dx10_bc6h_signed_mip.dds');
-    map8.anisotropy = 4;
-
-    const map9 = loader.load('textures/compressed/disturb_dx10_bc6h_unsigned_nomip.dds');
-    map9.anisotropy = 4;
-
-    const map10 = loader.load('textures/compressed/disturb_dx10_bc6h_unsigned_mip.dds');
-    map10.anisotropy = 4;
-
-    const cubemap1 = loader.load('textures/compressed/Mountains.dds', function (texture) {
-        texture.magFilter = THREE.LinearFilter;
-        texture.minFilter = THREE.LinearFilter;
-        texture.mapping = THREE.CubeReflectionMapping;
-        texture.colorSpace = THREE.SRGBColorSpace;
-        material1.needsUpdate = true;
-    });
-
-    const cubemap2 = loader.load('textures/compressed/Mountains_argb_mip.dds', function (texture) {
-        texture.magFilter = THREE.LinearFilter;
-        texture.minFilter = THREE.LinearFilter;
-        texture.mapping = THREE.CubeReflectionMapping;
-        texture.colorSpace = THREE.SRGBColorSpace;
-        material5.needsUpdate = true;
-    });
-
-    const cubemap3 = loader.load('textures/compressed/Mountains_argb_nomip.dds', function (texture) {
-        texture.magFilter = THREE.LinearFilter;
-        texture.minFilter = THREE.LinearFilter;
-        texture.mapping = THREE.CubeReflectionMapping;
-        texture.colorSpace = THREE.SRGBColorSpace;
-        material6.needsUpdate = true;
-    });
-
-    const material1 = new THREE.MeshBasicMaterial({ map: map1, envMap: cubemap1 });
-    const material2 = new THREE.MeshBasicMaterial({ map: map2 });
-    const material3 = new THREE.MeshBasicMaterial({ map: map3, alphaTest: 0.5, side: THREE.DoubleSide });
-    const material4 = new THREE.MeshBasicMaterial({
-        map: map4,
-        side: THREE.DoubleSide,
-        blending: THREE.AdditiveBlending,
-        depthTest: false,
-        transparent: true,
-    });
-    const material5 = new THREE.MeshBasicMaterial({ envMap: cubemap2 });
-    const material6 = new THREE.MeshBasicMaterial({ envMap: cubemap3 });
-    const material7 = new THREE.MeshBasicMaterial({ map: map5 });
-    const material8 = new THREE.MeshBasicMaterial({ map: map6 });
-    const material9 = new THREE.MeshBasicMaterial({ map: map7 });
-    const material10 = new THREE.MeshBasicMaterial({ map: map8 });
-    const material11 = new THREE.MeshBasicMaterial({ map: map9 });
-    const material12 = new THREE.MeshBasicMaterial({ map: map10 });
-
-    let mesh = new THREE.Mesh(new THREE.TorusGeometry(), material1);
-    mesh.position.x = -10;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material2);
-    mesh.position.x = -6;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material3);
-    mesh.position.x = -6;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material4);
-    mesh.position.x = -10;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material5);
-    mesh.position.x = -2;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material6);
-    mesh.position.x = -2;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material7);
-    mesh.position.x = 2;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material8);
-    mesh.position.x = 2;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material9);
-    mesh.position.x = 6;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material10);
-    mesh.position.x = 6;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material11);
-    mesh.position.x = 10;
-    mesh.position.y = -2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    mesh = new THREE.Mesh(geometry, material12);
-    mesh.position.x = 10;
-    mesh.position.y = 2;
-    scene.add(mesh);
-    meshes.push(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    for (let i = 0; i < meshes.length; i++) {
-        const mesh = meshes[i];
-        mesh.rotation.x = time;
-        mesh.rotation.y = time;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_texture_ktx.ts b/examples-testing/examples/webgl_loader_texture_ktx.ts
deleted file mode 100644
index af66eb810..000000000
--- a/examples-testing/examples/webgl_loader_texture_ktx.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import { KTXLoader } from 'three/addons/loaders/KTXLoader.js';
-
-/*
-	This is how compressed textures are supposed to be used:
-
-	best for desktop:
-	BC1(DXT1) - opaque textures
-	BC3(DXT5) - transparent textures with full alpha range
-
-	best for iOS:
-	PVR2, PVR4 - opaque textures or alpha
-
-	best for Android:
-	ETC1 - opaque textures
-	ASTC_4x4, ASTC8x8 - transparent textures with full alpha range
-	*/
-
-let camera, scene, renderer;
-const meshes = [];
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    const formats = {
-        astc: renderer.extensions.has('WEBGL_compressed_texture_astc'),
-        etc1: renderer.extensions.has('WEBGL_compressed_texture_etc1'),
-        s3tc: renderer.extensions.has('WEBGL_compressed_texture_s3tc'),
-        pvrtc: renderer.extensions.has('WEBGL_compressed_texture_pvrtc'),
-    };
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.BoxGeometry(200, 200, 200);
-    let material1, material2;
-
-    // TODO: add cubemap support
-    const loader = new KTXLoader();
-
-    if (formats.pvrtc) {
-        material1 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/disturb_PVR2bpp.ktx'),
-        });
-        material1.map.colorSpace = THREE.SRGBColorSpace;
-        material2 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/lensflare_PVR4bpp.ktx'),
-            depthTest: false,
-            transparent: true,
-            side: THREE.DoubleSide,
-        });
-        material2.map.colorSpace = THREE.SRGBColorSpace;
-
-        meshes.push(new THREE.Mesh(geometry, material1));
-        meshes.push(new THREE.Mesh(geometry, material2));
-    }
-
-    if (formats.s3tc) {
-        material1 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/disturb_BC1.ktx'),
-        });
-        material1.map.colorSpace = THREE.SRGBColorSpace;
-        material2 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/lensflare_BC3.ktx'),
-            depthTest: false,
-            transparent: true,
-            side: THREE.DoubleSide,
-        });
-        material2.map.colorSpace = THREE.SRGBColorSpace;
-
-        meshes.push(new THREE.Mesh(geometry, material1));
-        meshes.push(new THREE.Mesh(geometry, material2));
-    }
-
-    if (formats.etc1) {
-        material1 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/disturb_ETC1.ktx'),
-        });
-
-        meshes.push(new THREE.Mesh(geometry, material1));
-    }
-
-    if (formats.astc) {
-        material1 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/disturb_ASTC4x4.ktx'),
-        });
-        material1.map.colorSpace = THREE.SRGBColorSpace;
-        material2 = new THREE.MeshBasicMaterial({
-            map: loader.load('textures/compressed/lensflare_ASTC8x8.ktx'),
-            depthTest: false,
-            transparent: true,
-            side: THREE.DoubleSide,
-        });
-        material2.map.colorSpace = THREE.SRGBColorSpace;
-
-        meshes.push(new THREE.Mesh(geometry, material1));
-        meshes.push(new THREE.Mesh(geometry, material2));
-    }
-
-    let x = (-meshes.length / 2) * 150;
-    for (let i = 0; i < meshes.length; ++i, x += 300) {
-        const mesh = meshes[i];
-        mesh.position.x = x;
-        mesh.position.y = 0;
-        scene.add(mesh);
-    }
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = Date.now() * 0.001;
-
-    for (let i = 0; i < meshes.length; i++) {
-        const mesh = meshes[i];
-        mesh.rotation.x = time;
-        mesh.rotation.y = time;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_texture_rgbm.ts b/examples-testing/examples/webgl_loader_texture_rgbm.ts
deleted file mode 100644
index a882cdbc5..000000000
--- a/examples-testing/examples/webgl_loader_texture_rgbm.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
-
-const params = {
-    exposure: 2.0,
-};
-
-let renderer, scene, camera;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    renderer.toneMappingExposure = params.exposure;
-
-    scene = new THREE.Scene();
-
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0, 1);
-
-    new RGBMLoader().setMaxRange(16).load('textures/memorial.png', function (texture) {
-        const material = new THREE.MeshBasicMaterial({ map: texture });
-
-        const quad = new THREE.PlaneGeometry(1, 1.5);
-
-        const mesh = new THREE.Mesh(quad, material);
-
-        scene.add(mesh);
-
-        render();
-    });
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'exposure', 0, 4, 0.01).onChange(render);
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    const frustumHeight = camera.top - camera.bottom;
-
-    camera.left = (-frustumHeight * aspect) / 2;
-    camera.right = (frustumHeight * aspect) / 2;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.toneMappingExposure = params.exposure;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_texture_tga.ts b/examples-testing/examples/webgl_loader_texture_tga.ts
deleted file mode 100644
index c4f65b79a..000000000
--- a/examples-testing/examples/webgl_loader_texture_tga.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { TGALoader } from 'three/addons/loaders/TGALoader.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 1, 5);
-
-    scene = new THREE.Scene();
-
-    //
-
-    const loader = new TGALoader();
-    const geometry = new THREE.BoxGeometry();
-
-    // add box 1 - grey8 texture
-
-    const texture1 = loader.load('textures/crate_grey8.tga');
-    texture1.colorSpace = THREE.SRGBColorSpace;
-    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
-
-    const mesh1 = new THREE.Mesh(geometry, material1);
-    mesh1.position.x = -1;
-
-    scene.add(mesh1);
-
-    // add box 2 - tga texture
-
-    const texture2 = loader.load('textures/crate_color8.tga');
-    texture2.colorSpace = THREE.SRGBColorSpace;
-    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
-
-    const mesh2 = new THREE.Mesh(geometry, material2);
-    mesh2.position.x = 1;
-
-    scene.add(mesh2);
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
-    scene.add(ambientLight);
-
-    const light = new THREE.DirectionalLight(0xffffff, 2.5);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_loader_texture_tiff.ts b/examples-testing/examples/webgl_loader_texture_tiff.ts
deleted file mode 100644
index f097774aa..000000000
--- a/examples-testing/examples/webgl_loader_texture_tiff.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import * as THREE from 'three';
-
-import { TIFFLoader } from 'three/addons/loaders/TIFFLoader.js';
-
-let renderer, scene, camera;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.set(0, 0, 4);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    const loader = new TIFFLoader();
-
-    const geometry = new THREE.PlaneGeometry();
-
-    // uncompressed
-
-    loader.load('textures/tiff/crate_uncompressed.tif', function (texture) {
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        const material = new THREE.MeshBasicMaterial({ map: texture });
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(-1.5, 0, 0);
-
-        scene.add(mesh);
-
-        render();
-    });
-
-    // LZW
-
-    loader.load('textures/tiff/crate_lzw.tif', function (texture) {
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        const material = new THREE.MeshBasicMaterial({ map: texture });
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(0, 0, 0);
-
-        scene.add(mesh);
-
-        render();
-    });
-
-    // JPEG
-
-    loader.load('textures/tiff/crate_jpeg.tif', function (texture) {
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        const material = new THREE.MeshBasicMaterial({ map: texture });
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(1.5, 0, 0);
-
-        scene.add(mesh);
-
-        render();
-    });
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_texture_ultrahdr.ts b/examples-testing/examples/webgl_loader_texture_ultrahdr.ts
deleted file mode 100644
index c8bce4bf9..000000000
--- a/examples-testing/examples/webgl_loader_texture_ultrahdr.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-const params = {
-    autoRotate: true,
-    metalness: 1.0,
-    roughness: 0.0,
-    exposure: 1.0,
-    resolution: '2k',
-    type: 'HalfFloatType',
-};
-
-let renderer, scene, camera, controls, torusMesh, loader;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = params.exposure;
-
-    renderer.setAnimationLoop(render);
-
-    scene = new THREE.Scene();
-
-    torusMesh = new THREE.Mesh(
-        new THREE.TorusKnotGeometry(1, 0.4, 128, 128, 1, 3),
-        new THREE.MeshStandardMaterial({ roughness: params.roughness, metalness: params.metalness }),
-    );
-    scene.add(torusMesh);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.set(0.0, 0.0, -6.0);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-
-    loader = new UltraHDRLoader();
-    loader.setDataType(THREE.FloatType);
-
-    const loadEnvironment = function (resolution = '2k', type = 'HalfFloatType') {
-        loader.setDataType(THREE[type]);
-
-        loader.load(`textures/equirectangular/spruit_sunrise_${resolution}.hdr.jpg`, function (texture) {
-            texture.mapping = THREE.EquirectangularReflectionMapping;
-            texture.needsUpdate = true;
-
-            scene.background = texture;
-            scene.environment = texture;
-        });
-    };
-
-    loadEnvironment(params.resolution, params.type);
-
-    const gui = new GUI();
-
-    gui.add(params, 'autoRotate');
-    gui.add(params, 'metalness', 0, 1, 0.01);
-    gui.add(params, 'roughness', 0, 1, 0.01);
-    gui.add(params, 'exposure', 0, 4, 0.01);
-    gui.add(params, 'resolution', ['2k', '4k']).onChange(value => {
-        loadEnvironment(value, params.type);
-    });
-    gui.add(params, 'type', ['HalfFloatType', 'FloatType']).onChange(value => {
-        loadEnvironment(params.resolution, value);
-    });
-
-    gui.open();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render() {
-    torusMesh.material.roughness = params.roughness;
-    torusMesh.material.metalness = params.metalness;
-
-    if (params.autoRotate) {
-        torusMesh.rotation.y += 0.005;
-    }
-
-    renderer.toneMappingExposure = params.exposure;
-
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_tilt.ts b/examples-testing/examples/webgl_loader_tilt.ts
deleted file mode 100644
index 2a583c2b0..000000000
--- a/examples-testing/examples/webgl_loader_tilt.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { TiltLoader } from 'three/addons/loaders/TiltLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 500);
-
-    camera.position.y = 43;
-    camera.position.z = 100;
-
-    scene.add(camera);
-
-    const grid = new THREE.GridHelper(50, 50, 0xffffff, 0x555555);
-    scene.add(grid);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    const loader = new TiltLoader();
-    loader.load('./models/tilt/BRUSH_DOME.tilt', function (object) {
-        // console.log( object.children.length );
-        scene.add(object);
-        render();
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.target.y = camera.position.y;
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_ttf.ts b/examples-testing/examples/webgl_loader_ttf.ts
deleted file mode 100644
index 168371a14..000000000
--- a/examples-testing/examples/webgl_loader_ttf.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-import * as THREE from 'three';
-
-import { TTFLoader } from 'three/addons/loaders/TTFLoader.js';
-import { Font } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-let container;
-let camera, cameraTarget, scene, renderer;
-let group, textMesh1, textMesh2, textGeo, material;
-let firstLetter = true;
-
-let text = 'three.js';
-const depth = 20,
-    size = 70,
-    hover = 30,
-    curveSegments = 4,
-    bevelThickness = 2,
-    bevelSize = 1.5;
-
-let font = null;
-const mirror = true;
-
-let targetRotation = 0;
-let targetRotationOnPointerDown = 0;
-
-let pointerX = 0;
-let pointerXOnPointerDown = 0;
-
-let windowHalfX = window.innerWidth / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
-    camera.position.set(0, 400, 700);
-
-    cameraTarget = new THREE.Vector3(0, 150, 0);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x000000);
-    scene.fog = new THREE.Fog(0x000000, 250, 1400);
-
-    // LIGHTS
-
-    const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.4);
-    dirLight1.position.set(0, 0, 1).normalize();
-    scene.add(dirLight1);
-
-    const dirLight2 = new THREE.DirectionalLight(0xffffff, 2);
-    dirLight2.position.set(0, hover, 10).normalize();
-    dirLight2.color.setHSL(Math.random(), 1, 0.5, THREE.SRGBColorSpace);
-    scene.add(dirLight2);
-
-    material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
-
-    group = new THREE.Group();
-    group.position.y = 100;
-
-    scene.add(group);
-
-    const loader = new TTFLoader();
-
-    loader.load('fonts/ttf/kenpixel.ttf', function (json) {
-        font = new Font(json);
-        createText();
-    });
-
-    const plane = new THREE.Mesh(
-        new THREE.PlaneGeometry(10000, 10000),
-        new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }),
-    );
-    plane.position.y = 100;
-    plane.rotation.x = -Math.PI / 2;
-    scene.add(plane);
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // EVENTS
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointerdown', onPointerDown);
-
-    document.addEventListener('keypress', onDocumentKeyPress);
-    document.addEventListener('keydown', onDocumentKeyDown);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentKeyDown(event) {
-    if (firstLetter) {
-        firstLetter = false;
-        text = '';
-    }
-
-    const keyCode = event.keyCode;
-
-    // backspace
-
-    if (keyCode === 8) {
-        event.preventDefault();
-
-        text = text.substring(0, text.length - 1);
-        refreshText();
-
-        return false;
-    }
-}
-
-function onDocumentKeyPress(event) {
-    const keyCode = event.which;
-
-    // backspace
-
-    if (keyCode === 8) {
-        event.preventDefault();
-    } else {
-        const ch = String.fromCharCode(keyCode);
-        text += ch;
-
-        refreshText();
-    }
-}
-
-function createText() {
-    textGeo = new TextGeometry(text, {
-        font: font,
-
-        size: size,
-        depth: depth,
-        curveSegments: curveSegments,
-
-        bevelThickness: bevelThickness,
-        bevelSize: bevelSize,
-        bevelEnabled: true,
-    });
-
-    textGeo.computeBoundingBox();
-    textGeo.computeVertexNormals();
-
-    const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
-
-    textMesh1 = new THREE.Mesh(textGeo, material);
-
-    textMesh1.position.x = centerOffset;
-    textMesh1.position.y = hover;
-    textMesh1.position.z = 0;
-
-    textMesh1.rotation.x = 0;
-    textMesh1.rotation.y = Math.PI * 2;
-
-    group.add(textMesh1);
-
-    if (mirror) {
-        textMesh2 = new THREE.Mesh(textGeo, material);
-
-        textMesh2.position.x = centerOffset;
-        textMesh2.position.y = -hover;
-        textMesh2.position.z = depth;
-
-        textMesh2.rotation.x = Math.PI;
-        textMesh2.rotation.y = Math.PI * 2;
-
-        group.add(textMesh2);
-    }
-}
-
-function refreshText() {
-    group.remove(textMesh1);
-    if (mirror) group.remove(textMesh2);
-
-    if (!text) return;
-
-    createText();
-}
-
-function onPointerDown(event) {
-    if (event.isPrimary === false) return;
-
-    pointerXOnPointerDown = event.clientX - windowHalfX;
-    targetRotationOnPointerDown = targetRotation;
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    pointerX = event.clientX - windowHalfX;
-
-    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
-}
-
-function onPointerUp() {
-    if (event.isPrimary === false) return;
-
-    document.removeEventListener('pointermove', onPointerMove);
-    document.removeEventListener('pointerup', onPointerUp);
-}
-
-//
-
-function animate() {
-    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
-
-    camera.lookAt(cameraTarget);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_usdz.ts b/examples-testing/examples/webgl_loader_usdz.ts
deleted file mode 100644
index d75823d88..000000000
--- a/examples-testing/examples/webgl_loader_usdz.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { USDZLoader } from 'three/addons/loaders/USDZLoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0.75, -1.5);
-
-    scene = new THREE.Scene();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-
-    const usdzLoader = new USDZLoader().setPath('models/usdz/');
-
-    const [texture, model] = await Promise.all([
-        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
-        usdzLoader.loadAsync('saeukkang.usdz'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.backgroundBlurriness = 0.5;
-    scene.environment = texture;
-
-    // model
-
-    model.position.y = 0.25;
-    model.position.z = -0.25;
-    scene.add(model);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 2.0;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 8;
-    // controls.target.y = 15;
-    // controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_vox.ts b/examples-testing/examples/webgl_loader_vox.ts
deleted file mode 100644
index 061848012..000000000
--- a/examples-testing/examples/webgl_loader_vox.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { VOXLoader, VOXMesh } from 'three/addons/loaders/VOXLoader.js';
-
-let camera, controls, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.set(0.175, 0.075, 0.175);
-
-    scene = new THREE.Scene();
-    scene.add(camera);
-
-    // light
-
-    const hemiLight = new THREE.HemisphereLight(0xcccccc, 0x444444, 3);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 2.5);
-    dirLight.position.set(1.5, 3, 2.5);
-    scene.add(dirLight);
-
-    const dirLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
-    dirLight2.position.set(-1.5, -3, -2.5);
-    scene.add(dirLight2);
-
-    const loader = new VOXLoader();
-    loader.load('models/vox/monu10.vox', function (chunks) {
-        for (let i = 0; i < chunks.length; i++) {
-            const chunk = chunks[i];
-
-            // displayPalette( chunk.palette );
-
-            const mesh = new VOXMesh(chunk);
-            mesh.scale.setScalar(0.0015);
-            scene.add(mesh);
-        }
-    });
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 0.1;
-    controls.maxDistance = 0.5;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-/*
-			function displayPalette( palette ) {
-
-				const canvas = document.createElement( 'canvas' );
-				canvas.width = 8;
-				canvas.height = 32;
-				canvas.style.position = 'absolute';
-				canvas.style.top = '0';
-				canvas.style.width = '100px';
-				canvas.style.imageRendering = 'pixelated';
-				document.body.appendChild( canvas );
-
-				const context = canvas.getContext( '2d' );
-
-				for ( let c = 0; c < 256; c ++ ) {
-
-					const x = c % 8;
-					const y = Math.floor( c / 8 );
-
-					const hex = palette[ c + 1 ];
-					const r = hex >> 0 & 0xff;
-					const g = hex >> 8 & 0xff;
-					const b = hex >> 16 & 0xff;
-					context.fillStyle = `rgba(${r},${g},${b},1)`;
-					context.fillRect( x, 31 - y, 1, 1 );
-
-				}
-
-			}
-			*/
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_loader_vrml.ts b/examples-testing/examples/webgl_loader_vrml.ts
deleted file mode 100644
index fecf4bb45..000000000
--- a/examples-testing/examples/webgl_loader_vrml.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats, controls, loader;
-
-const params = {
-    asset: 'house',
-};
-
-const assets = [
-    'creaseAngle',
-    'crystal',
-    'house',
-    'elevationGrid1',
-    'elevationGrid2',
-    'extrusion1',
-    'extrusion2',
-    'extrusion3',
-    'lines',
-    'linesTransparent',
-    'meshWithLines',
-    'meshWithTexture',
-    'pixelTexture',
-    'points',
-];
-
-let vrmlScene;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1e10);
-    camera.position.set(-10, 5, 10);
-
-    scene = new THREE.Scene();
-    scene.add(camera);
-
-    // light
-
-    const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
-    scene.add(ambientLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
-    dirLight.position.set(200, 200, 200);
-    scene.add(dirLight);
-
-    loader = new VRMLLoader();
-    loadAsset(params.asset);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 200;
-    controls.enableDamping = true;
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'asset', assets).onChange(function (value) {
-        if (vrmlScene) {
-            vrmlScene.traverse(function (object) {
-                if (object.material) object.material.dispose();
-                if (object.material && object.material.map) object.material.map.dispose();
-                if (object.geometry) object.geometry.dispose();
-            });
-
-            scene.remove(vrmlScene);
-        }
-
-        loadAsset(value);
-    });
-}
-
-function loadAsset(asset) {
-    loader.load('models/vrml/' + asset + '.wrl', function (object) {
-        vrmlScene = object;
-        scene.add(object);
-        controls.reset();
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update(); // to support damping
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_loader_vtk.ts b/examples-testing/examples/webgl_loader_vtk.ts
deleted file mode 100644
index dfc798657..000000000
--- a/examples-testing/examples/webgl_loader_vtk.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { VTKLoader } from 'three/addons/loaders/VTKLoader.js';
-
-let stats;
-
-let camera, controls, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
-    camera.position.z = 0.2;
-
-    scene = new THREE.Scene();
-
-    scene.add(camera);
-
-    // light
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x000000, 3);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
-    dirLight.position.set(2, 2, 2);
-    scene.add(dirLight);
-
-    const loader = new VTKLoader();
-    loader.load('models/vtk/bunny.vtk', function (geometry) {
-        geometry.center();
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(-0.075, 0.005, 0);
-        mesh.scale.multiplyScalar(0.2);
-        scene.add(mesh);
-    });
-
-    const loader1 = new VTKLoader();
-    loader1.load('models/vtk/cube_ascii.vtp', function (geometry) {
-        geometry.computeVertexNormals();
-        geometry.center();
-
-        const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.set(-0.025, 0, 0);
-        mesh.scale.multiplyScalar(0.01);
-
-        scene.add(mesh);
-    });
-
-    const loader2 = new VTKLoader();
-    loader2.load('models/vtk/cube_binary.vtp', function (geometry) {
-        geometry.computeVertexNormals();
-        geometry.center();
-
-        const material = new THREE.MeshLambertMaterial({ color: 0x0000ff });
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.set(0.025, 0, 0);
-        mesh.scale.multiplyScalar(0.01);
-
-        scene.add(mesh);
-    });
-
-    const loader3 = new VTKLoader();
-    loader3.load('models/vtk/cube_no_compression.vtp', function (geometry) {
-        geometry.computeVertexNormals();
-        geometry.center();
-
-        const material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.set(0.075, 0, 0);
-        mesh.scale.multiplyScalar(0.01);
-
-        scene.add(mesh);
-    });
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    controls = new TrackballControls(camera, renderer.domElement);
-    controls.minDistance = 0.1;
-    controls.maxDistance = 0.5;
-    controls.rotateSpeed = 5.0;
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    controls.handleResize();
-}
-
-function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_loader_xyz.ts b/examples-testing/examples/webgl_loader_xyz.ts
deleted file mode 100644
index 90e009840..000000000
--- a/examples-testing/examples/webgl_loader_xyz.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as THREE from 'three';
-
-import { XYZLoader } from 'three/addons/loaders/XYZLoader.js';
-
-let camera, scene, renderer, clock;
-
-let points;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(10, 7, 10);
-
-    scene = new THREE.Scene();
-    scene.add(camera);
-    camera.lookAt(scene.position);
-
-    clock = new THREE.Clock();
-
-    const loader = new XYZLoader();
-    loader.load('models/xyz/helix_201.xyz', function (geometry) {
-        geometry.center();
-
-        const vertexColors = geometry.hasAttribute('color') === true;
-
-        const material = new THREE.PointsMaterial({ size: 0.1, vertexColors: vertexColors });
-
-        points = new THREE.Points(geometry, material);
-        scene.add(points);
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (points) {
-        points.rotation.x += delta * 0.2;
-        points.rotation.y += delta * 0.5;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_lod.ts b/examples-testing/examples/webgl_lod.ts
deleted file mode 100644
index 0bb9e7be0..000000000
--- a/examples-testing/examples/webgl_lod.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import * as THREE from 'three';
-
-import { FlyControls } from 'three/addons/controls/FlyControls.js';
-
-let container;
-
-let camera, scene, renderer, controls;
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 15000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 15000);
-
-    const pointLight = new THREE.PointLight(0xff2200, 3, 0, 0);
-    pointLight.position.set(0, 0, 0);
-    scene.add(pointLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(0, 0, 1).normalize();
-    scene.add(dirLight);
-
-    const geometry = [
-        [new THREE.IcosahedronGeometry(100, 16), 50],
-        [new THREE.IcosahedronGeometry(100, 8), 300],
-        [new THREE.IcosahedronGeometry(100, 4), 1000],
-        [new THREE.IcosahedronGeometry(100, 2), 2000],
-        [new THREE.IcosahedronGeometry(100, 1), 8000],
-    ];
-
-    const material = new THREE.MeshLambertMaterial({ color: 0xffffff, wireframe: true });
-
-    for (let j = 0; j < 1000; j++) {
-        const lod = new THREE.LOD();
-
-        for (let i = 0; i < geometry.length; i++) {
-            const mesh = new THREE.Mesh(geometry[i][0], material);
-            mesh.scale.set(1.5, 1.5, 1.5);
-            mesh.updateMatrix();
-            mesh.matrixAutoUpdate = false;
-            lod.addLevel(mesh, geometry[i][1]);
-        }
-
-        lod.position.x = 10000 * (0.5 - Math.random());
-        lod.position.y = 7500 * (0.5 - Math.random());
-        lod.position.z = 10000 * (0.5 - Math.random());
-        lod.updateMatrix();
-        lod.matrixAutoUpdate = false;
-        scene.add(lod);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    controls = new FlyControls(camera, renderer.domElement);
-    controls.movementSpeed = 1000;
-    controls.rollSpeed = Math.PI / 10;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update(clock.getDelta());
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_marchingcubes.ts b/examples-testing/examples/webgl_marchingcubes.ts
deleted file mode 100644
index d11df56a4..000000000
--- a/examples-testing/examples/webgl_marchingcubes.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { MarchingCubes } from 'three/addons/objects/MarchingCubes.js';
-import { ToonShader1, ToonShader2, ToonShaderHatching, ToonShaderDotted } from 'three/addons/shaders/ToonShader.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let materials, current_material;
-
-let light, pointLight, ambientLight;
-
-let effect, resolution;
-
-let effectController;
-
-let time = 0;
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(-500, 500, 1500);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    // LIGHTS
-
-    light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0.5, 0.5, 1);
-    scene.add(light);
-
-    pointLight = new THREE.PointLight(0xff7c00, 3, 0, 0);
-    pointLight.position.set(0, 0, 100);
-    scene.add(pointLight);
-
-    ambientLight = new THREE.AmbientLight(0x323232, 3);
-    scene.add(ambientLight);
-
-    // MATERIALS
-
-    materials = generateMaterials();
-    current_material = 'shiny';
-
-    // MARCHING CUBES
-
-    resolution = 28;
-
-    effect = new MarchingCubes(resolution, materials[current_material], true, true, 100000);
-    effect.position.set(0, 0, 0);
-    effect.scale.set(700, 700, 700);
-
-    effect.enableUvs = false;
-    effect.enableColors = false;
-
-    scene.add(effect);
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // CONTROLS
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 500;
-    controls.maxDistance = 5000;
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // GUI
-
-    setupGui();
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function generateMaterials() {
-    // environment map
-
-    const path = 'textures/cube/SwedishRoyalCastle/';
-    const format = '.jpg';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const cubeTextureLoader = new THREE.CubeTextureLoader();
-
-    const reflectionCube = cubeTextureLoader.load(urls);
-    const refractionCube = cubeTextureLoader.load(urls);
-    refractionCube.mapping = THREE.CubeRefractionMapping;
-
-    // toons
-
-    const toonMaterial1 = createShaderMaterial(ToonShader1, light, ambientLight);
-    const toonMaterial2 = createShaderMaterial(ToonShader2, light, ambientLight);
-    const hatchingMaterial = createShaderMaterial(ToonShaderHatching, light, ambientLight);
-    const dottedMaterial = createShaderMaterial(ToonShaderDotted, light, ambientLight);
-
-    const texture = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.wrapT = THREE.RepeatWrapping;
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const materials = {
-        shiny: new THREE.MeshStandardMaterial({
-            color: 0x9c0000,
-            envMap: reflectionCube,
-            roughness: 0.1,
-            metalness: 1.0,
-        }),
-        chrome: new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: reflectionCube }),
-        liquid: new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: refractionCube, refractionRatio: 0.85 }),
-        matte: new THREE.MeshPhongMaterial({ specular: 0x494949, shininess: 1 }),
-        flat: new THREE.MeshLambertMaterial({
-            /*TODO flatShading: true */
-        }),
-        textured: new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0x111111, shininess: 1, map: texture }),
-        colors: new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0xffffff, shininess: 2, vertexColors: true }),
-        multiColors: new THREE.MeshPhongMaterial({ shininess: 2, vertexColors: true }),
-        plastic: new THREE.MeshPhongMaterial({ specular: 0xc1c1c1, shininess: 250 }),
-        toon1: toonMaterial1,
-        toon2: toonMaterial2,
-        hatching: hatchingMaterial,
-        dotted: dottedMaterial,
-    };
-
-    return materials;
-}
-
-function createShaderMaterial(shader, light, ambientLight) {
-    const u = THREE.UniformsUtils.clone(shader.uniforms);
-
-    const vs = shader.vertexShader;
-    const fs = shader.fragmentShader;
-
-    const material = new THREE.ShaderMaterial({ uniforms: u, vertexShader: vs, fragmentShader: fs });
-
-    material.uniforms['uDirLightPos'].value = light.position;
-    material.uniforms['uDirLightColor'].value = light.color;
-
-    material.uniforms['uAmbientLightColor'].value = ambientLight.color;
-
-    return material;
-}
-
-//
-
-function setupGui() {
-    const createHandler = function (id) {
-        return function () {
-            current_material = id;
-
-            effect.material = materials[id];
-            effect.enableUvs = current_material === 'textured' ? true : false;
-            effect.enableColors = current_material === 'colors' || current_material === 'multiColors' ? true : false;
-        };
-    };
-
-    effectController = {
-        material: 'shiny',
-
-        speed: 1.0,
-        numBlobs: 10,
-        resolution: 28,
-        isolation: 80,
-
-        floor: true,
-        wallx: false,
-        wallz: false,
-
-        dummy: function () {},
-    };
-
-    let h;
-
-    const gui = new GUI();
-
-    // material (type)
-
-    h = gui.addFolder('Materials');
-
-    for (const m in materials) {
-        effectController[m] = createHandler(m);
-        h.add(effectController, m).name(m);
-    }
-
-    // simulation
-
-    h = gui.addFolder('Simulation');
-
-    h.add(effectController, 'speed', 0.1, 8.0, 0.05);
-    h.add(effectController, 'numBlobs', 1, 50, 1);
-    h.add(effectController, 'resolution', 14, 100, 1);
-    h.add(effectController, 'isolation', 10, 300, 1);
-
-    h.add(effectController, 'floor');
-    h.add(effectController, 'wallx');
-    h.add(effectController, 'wallz');
-}
-
-// this controls content of marching cubes voxel field
-
-function updateCubes(object, time, numblobs, floor, wallx, wallz) {
-    object.reset();
-
-    // fill the field with some metaballs
-
-    const rainbow = [
-        new THREE.Color(0xff0000),
-        new THREE.Color(0xffbb00),
-        new THREE.Color(0xffff00),
-        new THREE.Color(0x00ff00),
-        new THREE.Color(0x0000ff),
-        new THREE.Color(0x9400bd),
-        new THREE.Color(0xc800eb),
-    ];
-    const subtract = 12;
-    const strength = 1.2 / ((Math.sqrt(numblobs) - 1) / 4 + 1);
-
-    for (let i = 0; i < numblobs; i++) {
-        const ballx = Math.sin(i + 1.26 * time * (1.03 + 0.5 * Math.cos(0.21 * i))) * 0.27 + 0.5;
-        const bally = Math.abs(Math.cos(i + 1.12 * time * Math.cos(1.22 + 0.1424 * i))) * 0.77; // dip into the floor
-        const ballz = Math.cos(i + 1.32 * time * 0.1 * Math.sin(0.92 + 0.53 * i)) * 0.27 + 0.5;
-
-        if (current_material === 'multiColors') {
-            object.addBall(ballx, bally, ballz, strength, subtract, rainbow[i % 7]);
-        } else {
-            object.addBall(ballx, bally, ballz, strength, subtract);
-        }
-    }
-
-    if (floor) object.addPlaneY(2, 12);
-    if (wallz) object.addPlaneZ(2, 12);
-    if (wallx) object.addPlaneX(2, 12);
-
-    object.update();
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    time += delta * effectController.speed * 0.5;
-
-    // marching cubes
-
-    if (effectController.resolution !== resolution) {
-        resolution = effectController.resolution;
-        effect.init(Math.floor(resolution));
-    }
-
-    if (effectController.isolation !== effect.isolation) {
-        effect.isolation = effectController.isolation;
-    }
-
-    updateCubes(
-        effect,
-        time,
-        effectController.numBlobs,
-        effectController.floor,
-        effectController.wallx,
-        effectController.wallz,
-    );
-
-    // render
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_alphahash.ts b/examples-testing/examples/webgl_materials_alphahash.ts
deleted file mode 100644
index 1ecf95f26..000000000
--- a/examples-testing/examples/webgl_materials_alphahash.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, controls, stats, mesh, material;
-
-let composer, renderPass, taaRenderPass, outputPass;
-
-let needsUpdate = false;
-
-const amount = parseInt(window.location.search.slice(1)) || 3;
-const count = Math.pow(amount, 3);
-
-const color = new THREE.Color();
-
-const params = {
-    alpha: 0.5,
-    alphaHash: true,
-    taa: true,
-    sampleLevel: 2,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(amount, amount, amount);
-    camera.lookAt(0, 0, 0);
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.IcosahedronGeometry(0.5, 3);
-
-    material = new THREE.MeshStandardMaterial({
-        color: 0xffffff,
-        alphaHash: params.alphaHash,
-        opacity: params.alpha,
-    });
-
-    mesh = new THREE.InstancedMesh(geometry, material, count);
-
-    let i = 0;
-    const offset = (amount - 1) / 2;
-
-    const matrix = new THREE.Matrix4();
-
-    for (let x = 0; x < amount; x++) {
-        for (let y = 0; y < amount; y++) {
-            for (let z = 0; z < amount; z++) {
-                matrix.setPosition(offset - x, offset - y, offset - z);
-
-                mesh.setMatrixAt(i, matrix);
-                mesh.setColorAt(i, color.setHex(Math.random() * 0xffffff));
-
-                i++;
-            }
-        }
-    }
-
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene.environment = pmremGenerator.fromScene(environment).texture;
-    environment.dispose();
-
-    //
-
-    composer = new EffectComposer(renderer);
-
-    renderPass = new RenderPass(scene, camera);
-    renderPass.enabled = false;
-
-    taaRenderPass = new TAARenderPass(scene, camera);
-
-    outputPass = new OutputPass();
-
-    composer.addPass(renderPass);
-    composer.addPass(taaRenderPass);
-    composer.addPass(outputPass);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enablePan = false;
-
-    controls.addEventListener('change', () => (needsUpdate = true));
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'alpha', 0, 1).onChange(onMaterialUpdate);
-    gui.add(params, 'alphaHash').onChange(onMaterialUpdate);
-
-    const taaFolder = gui.addFolder('Temporal Anti-Aliasing');
-
-    taaFolder
-        .add(params, 'taa')
-        .name('enabled')
-        .onChange(() => {
-            renderPass.enabled = !params.taa;
-            taaRenderPass.enabled = params.taa;
-
-            sampleLevelCtrl.enable(params.taa);
-
-            needsUpdate = true;
-        });
-
-    const sampleLevelCtrl = taaFolder.add(params, 'sampleLevel', 0, 6, 1).onChange(() => (needsUpdate = true));
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-
-    needsUpdate = true;
-}
-
-function onMaterialUpdate() {
-    material.opacity = params.alpha;
-    material.alphaHash = params.alphaHash;
-    material.transparent = !params.alphaHash;
-    material.depthWrite = params.alphaHash;
-
-    material.needsUpdate = true;
-    needsUpdate = true;
-}
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    if (needsUpdate) {
-        taaRenderPass.accumulate = false;
-        taaRenderPass.sampleLevel = 0;
-
-        needsUpdate = false;
-    } else {
-        taaRenderPass.accumulate = true;
-        taaRenderPass.sampleLevel = params.sampleLevel;
-    }
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_materials_blending.ts b/examples-testing/examples/webgl_materials_blending.ts
deleted file mode 100644
index 11cc009bc..000000000
--- a/examples-testing/examples/webgl_materials_blending.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let mapBg;
-
-const textureLoader = new THREE.TextureLoader();
-
-init();
-
-function init() {
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 600;
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    // BACKGROUND
-
-    const canvas = document.createElement('canvas');
-    const ctx = canvas.getContext('2d');
-    canvas.width = canvas.height = 128;
-    ctx.fillStyle = '#ddd';
-    ctx.fillRect(0, 0, 128, 128);
-    ctx.fillStyle = '#555';
-    ctx.fillRect(0, 0, 64, 64);
-    ctx.fillStyle = '#999';
-    ctx.fillRect(32, 32, 32, 32);
-    ctx.fillStyle = '#555';
-    ctx.fillRect(64, 64, 64, 64);
-    ctx.fillStyle = '#777';
-    ctx.fillRect(96, 96, 32, 32);
-
-    mapBg = new THREE.CanvasTexture(canvas);
-    mapBg.colorSpace = THREE.SRGBColorSpace;
-    mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping;
-    mapBg.repeat.set(64, 32);
-
-    scene.background = mapBg;
-
-    // OBJECTS
-
-    const blendings = [
-        { name: 'No', constant: THREE.NoBlending },
-        { name: 'Normal', constant: THREE.NormalBlending },
-        { name: 'Additive', constant: THREE.AdditiveBlending },
-        { name: 'Subtractive', constant: THREE.SubtractiveBlending },
-        { name: 'Multiply', constant: THREE.MultiplyBlending },
-    ];
-
-    const assignSRGB = texture => {
-        texture.colorSpace = THREE.SRGBColorSpace;
-    };
-
-    const map0 = textureLoader.load('textures/uv_grid_opengl.jpg', assignSRGB);
-    const map1 = textureLoader.load('textures/sprite0.jpg', assignSRGB);
-    const map2 = textureLoader.load('textures/sprite0.png', assignSRGB);
-    const map3 = textureLoader.load('textures/lensflare/lensflare0.png', assignSRGB);
-    const map4 = textureLoader.load('textures/lensflare/lensflare0_alpha.png', assignSRGB);
-
-    const geo1 = new THREE.PlaneGeometry(100, 100);
-    const geo2 = new THREE.PlaneGeometry(100, 25);
-
-    addImageRow(map0, 300);
-    addImageRow(map1, 150);
-    addImageRow(map2, 0);
-    addImageRow(map3, -150);
-    addImageRow(map4, -300);
-
-    function addImageRow(map, y) {
-        for (let i = 0; i < blendings.length; i++) {
-            const blending = blendings[i];
-
-            const material = new THREE.MeshBasicMaterial({ map: map });
-            material.transparent = true;
-            material.blending = blending.constant;
-
-            const x = (i - blendings.length / 2) * 110;
-            const z = 0;
-
-            let mesh = new THREE.Mesh(geo1, material);
-            mesh.position.set(x, y, z);
-            scene.add(mesh);
-
-            mesh = new THREE.Mesh(geo2, generateLabelMaterial(blending.name));
-            mesh.position.set(x, y - 75, z);
-            scene.add(mesh);
-        }
-    }
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    const SCREEN_WIDTH = window.innerWidth;
-    const SCREEN_HEIGHT = window.innerHeight;
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-}
-
-function generateLabelMaterial(text) {
-    const canvas = document.createElement('canvas');
-    const ctx = canvas.getContext('2d');
-    canvas.width = 128;
-    canvas.height = 32;
-
-    ctx.fillStyle = 'rgba( 0, 0, 0, 0.95 )';
-    ctx.fillRect(0, 0, 128, 32);
-
-    ctx.fillStyle = 'white';
-    ctx.font = 'bold 12pt arial';
-    ctx.fillText(text, 10, 22);
-
-    const map = new THREE.CanvasTexture(canvas);
-    map.colorSpace = THREE.SRGBColorSpace;
-
-    const material = new THREE.MeshBasicMaterial({ map: map, transparent: true });
-
-    return material;
-}
-
-function animate() {
-    const time = Date.now() * 0.00025;
-    const ox = (time * -0.01 * mapBg.repeat.x) % 1;
-    const oy = (time * -0.01 * mapBg.repeat.y) % 1;
-
-    mapBg.offset.set(ox, oy);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_blending_custom.ts b/examples-testing/examples/webgl_materials_blending_custom.ts
deleted file mode 100644
index 072447426..000000000
--- a/examples-testing/examples/webgl_materials_blending_custom.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-let mapBg;
-const materials = [];
-
-const params = {
-    blendEquation: THREE.AddEquation,
-};
-
-const equations = {
-    Add: THREE.AddEquation,
-    Subtract: THREE.SubtractEquation,
-    ReverseSubtract: THREE.ReverseSubtractEquation,
-    Min: THREE.MinEquation,
-    Max: THREE.MaxEquation,
-};
-
-init();
-
-function init() {
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 700;
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    // BACKGROUND
-
-    const canvas = document.createElement('canvas');
-    const ctx = canvas.getContext('2d');
-    canvas.width = canvas.height = 128;
-    ctx.fillStyle = '#ddd';
-    ctx.fillRect(0, 0, 128, 128);
-    ctx.fillStyle = '#555';
-    ctx.fillRect(0, 0, 64, 64);
-    ctx.fillStyle = '#999';
-    ctx.fillRect(32, 32, 32, 32);
-    ctx.fillStyle = '#555';
-    ctx.fillRect(64, 64, 64, 64);
-    ctx.fillStyle = '#777';
-    ctx.fillRect(96, 96, 32, 32);
-
-    mapBg = new THREE.CanvasTexture(canvas);
-    mapBg.colorSpace = THREE.SRGBColorSpace;
-    mapBg.wrapS = mapBg.wrapT = THREE.RepeatWrapping;
-    mapBg.repeat.set(64, 32);
-
-    scene.background = mapBg;
-
-    // FOREGROUND OBJECTS
-
-    const src = [
-        { name: 'Zero', constant: THREE.ZeroFactor },
-        { name: 'One', constant: THREE.OneFactor },
-        { name: 'SrcColor', constant: THREE.SrcColorFactor },
-        { name: 'OneMinusSrcColor', constant: THREE.OneMinusSrcColorFactor },
-        { name: 'SrcAlpha', constant: THREE.SrcAlphaFactor },
-        { name: 'OneMinusSrcAlpha', constant: THREE.OneMinusSrcAlphaFactor },
-        { name: 'DstAlpha', constant: THREE.DstAlphaFactor },
-        { name: 'OneMinusDstAlpha', constant: THREE.OneMinusDstAlphaFactor },
-        { name: 'DstColor', constant: THREE.DstColorFactor },
-        { name: 'OneMinusDstColor', constant: THREE.OneMinusDstColorFactor },
-        { name: 'SrcAlphaSaturate', constant: THREE.SrcAlphaSaturateFactor },
-    ];
-
-    const dst = [
-        { name: 'Zero', constant: THREE.ZeroFactor },
-        { name: 'One', constant: THREE.OneFactor },
-        { name: 'SrcColor', constant: THREE.SrcColorFactor },
-        { name: 'OneMinusSrcColor', constant: THREE.OneMinusSrcColorFactor },
-        { name: 'SrcAlpha', constant: THREE.SrcAlphaFactor },
-        { name: 'OneMinusSrcAlpha', constant: THREE.OneMinusSrcAlphaFactor },
-        { name: 'DstAlpha', constant: THREE.DstAlphaFactor },
-        { name: 'OneMinusDstAlpha', constant: THREE.OneMinusDstAlphaFactor },
-        { name: 'DstColor', constant: THREE.DstColorFactor },
-        { name: 'OneMinusDstColor', constant: THREE.OneMinusDstColorFactor },
-    ];
-
-    const geo1 = new THREE.PlaneGeometry(100, 100);
-    const geo2 = new THREE.PlaneGeometry(100, 25);
-
-    const texture = new THREE.TextureLoader().load('textures/lensflare/lensflare0_alpha.png');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    for (let i = 0; i < dst.length; i++) {
-        const blendDst = dst[i];
-
-        for (let j = 0; j < src.length; j++) {
-            const blendSrc = src[j];
-
-            const material = new THREE.MeshBasicMaterial({ map: texture });
-            material.transparent = true;
-
-            material.blending = THREE.CustomBlending;
-            material.blendSrc = blendSrc.constant;
-            material.blendDst = blendDst.constant;
-            material.blendEquation = THREE.AddEquation;
-
-            const x = (j - src.length / 2) * 110;
-            const z = 0;
-            const y = (i - dst.length / 2) * 110 + 50;
-
-            const mesh = new THREE.Mesh(geo1, material);
-            mesh.position.set(x, -y, z);
-            mesh.matrixAutoUpdate = false;
-            mesh.updateMatrix();
-            scene.add(mesh);
-
-            materials.push(material);
-        }
-    }
-
-    for (let j = 0; j < src.length; j++) {
-        const blendSrc = src[j];
-
-        const x = (j - src.length / 2) * 110;
-        const z = 0;
-        const y = (0 - dst.length / 2) * 110 + 50;
-
-        const mesh = new THREE.Mesh(geo2, generateLabelMaterial(blendSrc.name, 'rgba( 0, 150, 0, 1 )'));
-        mesh.position.set(x, -(y - 70), z);
-        mesh.matrixAutoUpdate = false;
-        mesh.updateMatrix();
-        scene.add(mesh);
-    }
-
-    for (let i = 0; i < dst.length; i++) {
-        const blendDst = dst[i];
-
-        const x = (0 - src.length / 2) * 110 - 125;
-        const z = 0;
-        const y = (i - dst.length / 2) * 110 + 165;
-
-        const mesh = new THREE.Mesh(geo2, generateLabelMaterial(blendDst.name, 'rgba( 150, 0, 0, 1 )'));
-        mesh.position.set(x, -(y - 120), z);
-        mesh.matrixAutoUpdate = false;
-        mesh.updateMatrix();
-        scene.add(mesh);
-    }
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-
-    // GUI
-
-    //
-    const gui = new GUI({ width: 300 });
-
-    gui.add(params, 'blendEquation', equations).onChange(updateBlendEquation);
-    gui.open();
-}
-
-//
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-//
-
-function generateLabelMaterial(text, bg) {
-    const canvas = document.createElement('canvas');
-    const ctx = canvas.getContext('2d');
-    canvas.width = 128;
-    canvas.height = 32;
-
-    ctx.fillStyle = bg;
-    ctx.fillRect(0, 0, 128, 32);
-
-    ctx.fillStyle = 'white';
-    ctx.font = 'bold 11pt arial';
-    ctx.fillText(text, 8, 22);
-
-    const map = new THREE.CanvasTexture(canvas);
-    map.colorSpace = THREE.SRGBColorSpace;
-
-    const material = new THREE.MeshBasicMaterial({ map: map, transparent: true });
-    return material;
-}
-
-function updateBlendEquation(value) {
-    for (const material of materials) {
-        material.blendEquation = value;
-    }
-}
-
-function animate() {
-    const time = Date.now() * 0.00025;
-    const ox = (time * -0.01 * mapBg.repeat.x) % 1;
-    const oy = (time * -0.01 * mapBg.repeat.y) % 1;
-
-    mapBg.offset.set(ox, oy);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_bumpmap.ts b/examples-testing/examples/webgl_materials_bumpmap.ts
deleted file mode 100644
index d954fab7e..000000000
--- a/examples-testing/examples/webgl_materials_bumpmap.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let container, stats, loader;
-
-let camera, scene, renderer;
-
-let mesh;
-
-let spotLight;
-
-let mouseX = 0;
-let mouseY = 0;
-
-let targetX = 0;
-let targetY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 12;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x060708);
-
-    // LIGHTS
-
-    scene.add(new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3));
-
-    spotLight = new THREE.SpotLight(0xffffde, 200);
-    spotLight.position.set(3.5, 0, 7);
-    scene.add(spotLight);
-
-    spotLight.castShadow = true;
-
-    spotLight.shadow.mapSize.width = 2048;
-    spotLight.shadow.mapSize.height = 2048;
-
-    spotLight.shadow.camera.near = 2;
-    spotLight.shadow.camera.far = 15;
-
-    spotLight.shadow.camera.fov = 40;
-
-    spotLight.shadow.bias = -0.005;
-
-    //
-
-    const mapHeight = new THREE.TextureLoader().load(
-        'models/gltf/LeePerrySmith/Infinite-Level_02_Disp_NoSmoothUV-4096.jpg',
-    );
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0x9c6e49,
-        specular: 0x666666,
-        shininess: 25,
-        bumpMap: mapHeight,
-        bumpScale: 10,
-    });
-
-    loader = new GLTFLoader();
-    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        createScene(gltf.scene.children[0].geometry, 1, material);
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    renderer.shadowMap.enabled = true;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // EVENTS
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createScene(geometry, scale, material) {
-    mesh = new THREE.Mesh(geometry, material);
-
-    mesh.position.y = -0.5;
-    mesh.scale.set(scale, scale, scale);
-
-    mesh.castShadow = true;
-    mesh.receiveShadow = true;
-
-    scene.add(mesh);
-}
-
-//
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    targetX = mouseX * 0.001;
-    targetY = mouseY * 0.001;
-
-    if (mesh) {
-        mesh.rotation.y += 0.05 * (targetX - mesh.rotation.y);
-        mesh.rotation.x += 0.05 * (targetY - mesh.rotation.x);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_car.ts b/examples-testing/examples/webgl_materials_car.ts
deleted file mode 100644
index e810f7b7d..000000000
--- a/examples-testing/examples/webgl_materials_car.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer;
-let stats;
-
-let grid;
-let controls;
-
-const wheels = [];
-
-function init() {
-    const container = document.getElementById('container');
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 0.85;
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(4.25, 1.4, -4.5);
-
-    controls = new OrbitControls(camera, container);
-    controls.maxDistance = 9;
-    controls.maxPolarAngle = THREE.MathUtils.degToRad(90);
-    controls.target.set(0, 0.5, 0);
-    controls.update();
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x333333);
-    scene.environment = new RGBELoader().load('textures/equirectangular/venice_sunset_1k.hdr');
-    scene.environment.mapping = THREE.EquirectangularReflectionMapping;
-    scene.fog = new THREE.Fog(0x333333, 10, 15);
-
-    grid = new THREE.GridHelper(20, 40, 0xffffff, 0xffffff);
-    grid.material.opacity = 0.2;
-    grid.material.depthWrite = false;
-    grid.material.transparent = true;
-    scene.add(grid);
-
-    // materials
-
-    const bodyMaterial = new THREE.MeshPhysicalMaterial({
-        color: 0xff0000,
-        metalness: 1.0,
-        roughness: 0.5,
-        clearcoat: 1.0,
-        clearcoatRoughness: 0.03,
-    });
-
-    const detailsMaterial = new THREE.MeshStandardMaterial({
-        color: 0xffffff,
-        metalness: 1.0,
-        roughness: 0.5,
-    });
-
-    const glassMaterial = new THREE.MeshPhysicalMaterial({
-        color: 0xffffff,
-        metalness: 0.25,
-        roughness: 0,
-        transmission: 1.0,
-    });
-
-    const bodyColorInput = document.getElementById('body-color');
-    bodyColorInput.addEventListener('input', function () {
-        bodyMaterial.color.set(this.value);
-    });
-
-    const detailsColorInput = document.getElementById('details-color');
-    detailsColorInput.addEventListener('input', function () {
-        detailsMaterial.color.set(this.value);
-    });
-
-    const glassColorInput = document.getElementById('glass-color');
-    glassColorInput.addEventListener('input', function () {
-        glassMaterial.color.set(this.value);
-    });
-
-    // Car
-
-    const shadow = new THREE.TextureLoader().load('models/gltf/ferrari_ao.png');
-
-    const dracoLoader = new DRACOLoader();
-    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
-
-    const loader = new GLTFLoader();
-    loader.setDRACOLoader(dracoLoader);
-
-    loader.load('models/gltf/ferrari.glb', function (gltf) {
-        const carModel = gltf.scene.children[0];
-
-        carModel.getObjectByName('body').material = bodyMaterial;
-
-        carModel.getObjectByName('rim_fl').material = detailsMaterial;
-        carModel.getObjectByName('rim_fr').material = detailsMaterial;
-        carModel.getObjectByName('rim_rr').material = detailsMaterial;
-        carModel.getObjectByName('rim_rl').material = detailsMaterial;
-        carModel.getObjectByName('trim').material = detailsMaterial;
-
-        carModel.getObjectByName('glass').material = glassMaterial;
-
-        wheels.push(
-            carModel.getObjectByName('wheel_fl'),
-            carModel.getObjectByName('wheel_fr'),
-            carModel.getObjectByName('wheel_rl'),
-            carModel.getObjectByName('wheel_rr'),
-        );
-
-        // shadow
-        const mesh = new THREE.Mesh(
-            new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4),
-            new THREE.MeshBasicMaterial({
-                map: shadow,
-                blending: THREE.MultiplyBlending,
-                toneMapped: false,
-                transparent: true,
-            }),
-        );
-        mesh.rotation.x = -Math.PI / 2;
-        mesh.renderOrder = 2;
-        carModel.add(mesh);
-
-        scene.add(carModel);
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-
-    const time = -performance.now() / 1000;
-
-    for (let i = 0; i < wheels.length; i++) {
-        wheels[i].rotation.x = time * Math.PI * 2;
-    }
-
-    grid.position.z = -time % 1;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
-
-init();
diff --git a/examples-testing/examples/webgl_materials_cubemap.ts b/examples-testing/examples/webgl_materials_cubemap.ts
deleted file mode 100644
index 5f2692751..000000000
--- a/examples-testing/examples/webgl_materials_cubemap.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let pointLight;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 13;
-
-    //cubemap
-    const path = 'textures/cube/SwedishRoyalCastle/';
-    const format = '.jpg';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
-    const refractionCube = new THREE.CubeTextureLoader().load(urls);
-    refractionCube.mapping = THREE.CubeRefractionMapping;
-
-    scene = new THREE.Scene();
-    scene.background = reflectionCube;
-
-    //lights
-    const ambient = new THREE.AmbientLight(0xffffff, 3);
-    scene.add(ambient);
-
-    pointLight = new THREE.PointLight(0xffffff, 200);
-    scene.add(pointLight);
-
-    //materials
-    const cubeMaterial3 = new THREE.MeshLambertMaterial({
-        color: 0xffaa00,
-        envMap: reflectionCube,
-        combine: THREE.MixOperation,
-        reflectivity: 0.3,
-    });
-    const cubeMaterial2 = new THREE.MeshLambertMaterial({
-        color: 0xfff700,
-        envMap: refractionCube,
-        refractionRatio: 0.95,
-    });
-    const cubeMaterial1 = new THREE.MeshLambertMaterial({ color: 0xffffff, envMap: reflectionCube });
-
-    //models
-    const objLoader = new OBJLoader();
-
-    objLoader.setPath('models/obj/walt/');
-    objLoader.load('WaltHead.obj', function (object) {
-        const head = object.children[0];
-        head.scale.setScalar(0.1);
-        head.position.y = -3;
-        head.material = cubeMaterial1;
-
-        const head2 = head.clone();
-        head2.position.x = -6;
-        head2.material = cubeMaterial2;
-
-        const head3 = head.clone();
-        head3.position.x = 6;
-        head3.material = cubeMaterial3;
-
-        scene.add(head, head2, head3);
-    });
-
-    //renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enablePan = false;
-    controls.minPolarAngle = Math.PI / 4;
-    controls.maxPolarAngle = Math.PI / 1.5;
-
-    //stats
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_materials_cubemap_dynamic.ts b/examples-testing/examples/webgl_materials_cubemap_dynamic.ts
deleted file mode 100644
index 13a268901..000000000
--- a/examples-testing/examples/webgl_materials_cubemap_dynamic.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats;
-let cube, sphere, torus, material;
-
-let cubeCamera, cubeRenderTarget;
-
-let controls;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResized);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 75;
-
-    scene = new THREE.Scene();
-    scene.rotation.y = 0.5; // avoid flying objects occluding the sun
-
-    new RGBELoader().setPath('textures/equirectangular/').load('quarry_01_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-    });
-
-    //
-
-    cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
-    cubeRenderTarget.texture.type = THREE.HalfFloatType;
-
-    cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget);
-
-    //
-
-    material = new THREE.MeshStandardMaterial({
-        envMap: cubeRenderTarget.texture,
-        roughness: 0.05,
-        metalness: 1,
-    });
-
-    const gui = new GUI();
-    gui.add(material, 'roughness', 0, 1);
-    gui.add(material, 'metalness', 0, 1);
-    gui.add(renderer, 'toneMappingExposure', 0, 2).name('exposure');
-
-    sphere = new THREE.Mesh(new THREE.IcosahedronGeometry(15, 8), material);
-    scene.add(sphere);
-
-    const material2 = new THREE.MeshStandardMaterial({
-        roughness: 0.1,
-        metalness: 0,
-    });
-
-    cube = new THREE.Mesh(new THREE.BoxGeometry(15, 15, 15), material2);
-    scene.add(cube);
-
-    torus = new THREE.Mesh(new THREE.TorusKnotGeometry(8, 3, 128, 16), material2);
-    scene.add(torus);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-}
-
-function onWindowResized() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-function animate(msTime) {
-    const time = msTime / 1000;
-
-    cube.position.x = Math.cos(time) * 30;
-    cube.position.y = Math.sin(time) * 30;
-    cube.position.z = Math.sin(time) * 30;
-
-    cube.rotation.x += 0.02;
-    cube.rotation.y += 0.03;
-
-    torus.position.x = Math.cos(time + 10) * 30;
-    torus.position.y = Math.sin(time + 10) * 30;
-    torus.position.z = Math.sin(time + 10) * 30;
-
-    torus.rotation.x += 0.02;
-    torus.rotation.y += 0.03;
-
-    cubeCamera.update(renderer, scene);
-
-    controls.update();
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts b/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
deleted file mode 100644
index 944f4c18e..000000000
--- a/examples-testing/examples/webgl_materials_cubemap_mipmaps.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container;
-
-let camera, scene, renderer;
-
-init();
-
-//load customized cube texture
-async function loadCubeTextureWithMipmaps() {
-    const path = 'textures/cube/angus/';
-    const format = '.jpg';
-    const mipmaps = [];
-    const maxLevel = 8;
-
-    async function loadCubeTexture(urls) {
-        return new Promise(function (resolve) {
-            new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
-                resolve(cubeTexture);
-            });
-        });
-    }
-
-    // load mipmaps
-    const pendings = [];
-
-    for (let level = 0; level <= maxLevel; ++level) {
-        const urls = [];
-
-        for (let face = 0; face < 6; ++face) {
-            urls.push(path + 'cube_m0' + level + '_c0' + face + format);
-        }
-
-        const mipmapLevel = level;
-
-        pendings.push(
-            loadCubeTexture(urls).then(function (cubeTexture) {
-                mipmaps[mipmapLevel] = cubeTexture;
-            }),
-        );
-    }
-
-    await Promise.all(pendings);
-
-    const customizedCubeTexture = mipmaps.shift();
-    customizedCubeTexture.mipmaps = mipmaps;
-    customizedCubeTexture.colorSpace = THREE.SRGBColorSpace;
-    customizedCubeTexture.minFilter = THREE.LinearMipMapLinearFilter;
-    customizedCubeTexture.magFilter = THREE.LinearFilter;
-    customizedCubeTexture.generateMipmaps = false;
-    customizedCubeTexture.needsUpdate = true;
-
-    return customizedCubeTexture;
-}
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    loadCubeTextureWithMipmaps().then(function (cubeTexture) {
-        //model
-        const sphere = new THREE.SphereGeometry(100, 128, 128);
-
-        //manual mipmaps
-        let material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: cubeTexture });
-        material.name = 'manual mipmaps';
-
-        let mesh = new THREE.Mesh(sphere, material);
-        mesh.position.set(100, 0, 0);
-        scene.add(mesh);
-
-        //webgl mipmaps
-        material = material.clone();
-        material.name = 'auto mipmaps';
-
-        const autoCubeTexture = cubeTexture.clone();
-        autoCubeTexture.mipmaps = [];
-        autoCubeTexture.generateMipmaps = true;
-        autoCubeTexture.needsUpdate = true;
-
-        material.envMap = autoCubeTexture;
-
-        mesh = new THREE.Mesh(sphere, material);
-        mesh.position.set(-100, 0, 0);
-        scene.add(mesh);
-    });
-
-    //renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minPolarAngle = Math.PI / 4;
-    controls.maxPolarAngle = Math.PI / 1.5;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_cubemap_refraction.ts b/examples-testing/examples/webgl_materials_cubemap_refraction.ts
deleted file mode 100644
index 8c025071f..000000000
--- a/examples-testing/examples/webgl_materials_cubemap_refraction.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 100000);
-    camera.position.z = -4000;
-
-    //
-
-    const r = 'textures/cube/Park3Med/';
-
-    const urls = [r + 'px.jpg', r + 'nx.jpg', r + 'py.jpg', r + 'ny.jpg', r + 'pz.jpg', r + 'nz.jpg'];
-
-    const textureCube = new THREE.CubeTextureLoader().load(urls);
-    textureCube.mapping = THREE.CubeRefractionMapping;
-
-    scene = new THREE.Scene();
-    scene.background = textureCube;
-
-    // LIGHTS
-
-    const ambient = new THREE.AmbientLight(0xffffff, 3.5);
-    scene.add(ambient);
-
-    // material samples
-
-    const cubeMaterial3 = new THREE.MeshPhongMaterial({
-        color: 0xccddff,
-        envMap: textureCube,
-        refractionRatio: 0.98,
-        reflectivity: 0.9,
-    });
-    const cubeMaterial2 = new THREE.MeshPhongMaterial({ color: 0xccfffd, envMap: textureCube, refractionRatio: 0.985 });
-    const cubeMaterial1 = new THREE.MeshPhongMaterial({ color: 0xffffff, envMap: textureCube, refractionRatio: 0.98 });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const loader = new PLYLoader();
-    loader.load('models/ply/binary/Lucy100k.ply', function (geometry) {
-        createScene(geometry, cubeMaterial1, cubeMaterial2, cubeMaterial3);
-    });
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function createScene(geometry, m1, m2, m3) {
-    geometry.computeVertexNormals();
-
-    const s = 1.5;
-
-    let mesh = new THREE.Mesh(geometry, m1);
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry, m2);
-    mesh.position.x = -1500;
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry, m3);
-    mesh.position.x = 1500;
-    mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
-    scene.add(mesh);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) * 4;
-    mouseY = (event.clientY - windowHalfY) * 4;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts b/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
deleted file mode 100644
index 599a1369b..000000000
--- a/examples-testing/examples/webgl_materials_cubemap_render_to_mipmaps.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container;
-let camera, scene, renderer;
-
-const CubemapFilterShader = {
-    name: 'CubemapFilterShader',
-
-    uniforms: {
-        cubeTexture: { value: null },
-        mipIndex: { value: 0 },
-    },
-
-    vertexShader: /* glsl */ `
-
-					varying vec3 vWorldDirection;
-
-					#include <common>
-
-					void main() {
-						vWorldDirection = transformDirection(position, modelMatrix);
-						#include <begin_vertex>
-						#include <project_vertex>
-						gl_Position.z = gl_Position.w; // set z to camera.far
-					}
-
-					`,
-
-    fragmentShader: /* glsl */ `
-
-					uniform samplerCube cubeTexture;
-					varying vec3 vWorldDirection;
-
-					uniform float mipIndex;
-
-					#include <common>
-
-					void main() {
-						vec3 cubeCoordinates = normalize(vWorldDirection);
-
-						// Colorize mip levels
-						vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
-						if (mipIndex == 0.0) color.rgb = vec3(1.0, 1.0, 1.0);
-						else if (mipIndex == 1.0) color.rgb = vec3(0.0, 0.0, 1.0);
-						else if (mipIndex == 2.0) color.rgb = vec3(0.0, 1.0, 1.0);
-						else if (mipIndex == 3.0) color.rgb = vec3(0.0, 1.0, 0.0);
-						else if (mipIndex == 4.0) color.rgb = vec3(1.0, 1.0, 0.0);
-
-						gl_FragColor = textureCube(cubeTexture, cubeCoordinates, 0.0) * color;
-					}
-
-					`,
-};
-
-init();
-
-async function loadCubeTexture(urls) {
-    return new Promise(function (resolve) {
-        new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
-            resolve(cubeTexture);
-        });
-    });
-}
-
-function allocateCubemapRenderTarget(cubeMapSize) {
-    const params = {
-        magFilter: THREE.LinearFilter,
-        minFilter: THREE.LinearMipMapLinearFilter,
-        generateMipmaps: false,
-        type: THREE.HalfFloatType,
-        format: THREE.RGBAFormat,
-        colorSpace: THREE.LinearSRGBColorSpace,
-        depthBuffer: false,
-    };
-
-    const rt = new THREE.WebGLCubeRenderTarget(cubeMapSize, params);
-
-    const mipLevels = Math.log(cubeMapSize) * Math.LOG2E + 1.0;
-    for (let i = 0; i < mipLevels; i++) rt.texture.mipmaps.push({});
-
-    rt.texture.mapping = THREE.CubeReflectionMapping;
-    return rt;
-}
-
-function renderToCubeTexture(cubeMapRenderTarget, sourceCubeTexture) {
-    const geometry = new THREE.BoxGeometry(5, 5, 5);
-
-    const material = new THREE.ShaderMaterial({
-        name: CubemapFilterShader.name,
-        uniforms: THREE.UniformsUtils.clone(CubemapFilterShader.uniforms),
-        vertexShader: CubemapFilterShader.vertexShader,
-        fragmentShader: CubemapFilterShader.fragmentShader,
-        side: THREE.BackSide,
-        blending: THREE.NoBlending,
-    });
-
-    material.uniforms.cubeTexture.value = sourceCubeTexture;
-
-    const mesh = new THREE.Mesh(geometry, material);
-    const cubeCamera = new THREE.CubeCamera(1, 10, cubeMapRenderTarget);
-    const mipmapCount = Math.floor(Math.log2(Math.max(cubeMapRenderTarget.width, cubeMapRenderTarget.height)));
-
-    for (let mipmap = 0; mipmap < mipmapCount; mipmap++) {
-        material.uniforms.mipIndex.value = mipmap;
-        material.needsUpdate = true;
-
-        cubeMapRenderTarget.viewport.set(
-            0,
-            0,
-            cubeMapRenderTarget.width >> mipmap,
-            cubeMapRenderTarget.height >> mipmap,
-        );
-
-        cubeCamera.activeMipmapLevel = mipmap;
-        cubeCamera.update(renderer, mesh);
-    }
-
-    mesh.geometry.dispose();
-    mesh.material.dispose();
-}
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // Create renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 500;
-
-    // Create controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minPolarAngle = Math.PI / 4;
-    controls.maxPolarAngle = Math.PI / 1.5;
-
-    window.addEventListener('resize', onWindowResize);
-
-    // Load a cube texture
-    const r = 'textures/cube/Park3Med/';
-    const urls = [r + 'px.jpg', r + 'nx.jpg', r + 'py.jpg', r + 'ny.jpg', r + 'pz.jpg', r + 'nz.jpg'];
-
-    loadCubeTexture(urls).then(cubeTexture => {
-        // Allocate a cube map render target
-        const cubeMapRenderTarget = allocateCubemapRenderTarget(512);
-
-        // Render to all the mip levels of cubeMapRenderTarget
-        renderToCubeTexture(cubeMapRenderTarget, cubeTexture);
-
-        // Create geometry
-        const sphere = new THREE.SphereGeometry(100, 128, 128);
-        let material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: cubeTexture });
-
-        let mesh = new THREE.Mesh(sphere, material);
-        mesh.position.set(-100, 0, 0);
-        scene.add(mesh);
-
-        material = material.clone();
-        material.envMap = cubeMapRenderTarget.texture;
-
-        mesh = new THREE.Mesh(sphere, material);
-        mesh.position.set(100, 0, 0);
-        scene.add(mesh);
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_displacementmap.ts b/examples-testing/examples/webgl_materials_displacementmap.ts
deleted file mode 100644
index fd0be9a5e..000000000
--- a/examples-testing/examples/webgl_materials_displacementmap.ts
+++ /dev/null
@@ -1,224 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-
-let stats;
-let camera, scene, renderer, controls;
-
-const settings = {
-    metalness: 1.0,
-    roughness: 0.4,
-    ambientIntensity: 0.2,
-    aoMapIntensity: 1.0,
-    envMapIntensity: 1.0,
-    displacementScale: 2.436143, // from original model
-    normalScale: 1.0,
-};
-
-let mesh, material;
-
-let pointLight, ambientLight;
-
-const height = 500; // of camera frustum
-
-let r = 0.0;
-
-init();
-initGui();
-
-// Init gui
-function initGui() {
-    const gui = new GUI();
-    //let gui = gui.addFolder( "Material" );
-    gui.add(settings, 'metalness')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.metalness = value;
-        });
-
-    gui.add(settings, 'roughness')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.roughness = value;
-        });
-
-    gui.add(settings, 'aoMapIntensity')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.aoMapIntensity = value;
-        });
-
-    gui.add(settings, 'ambientIntensity')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            ambientLight.intensity = value;
-        });
-
-    gui.add(settings, 'envMapIntensity')
-        .min(0)
-        .max(3)
-        .onChange(function (value) {
-            material.envMapIntensity = value;
-        });
-
-    gui.add(settings, 'displacementScale')
-        .min(0)
-        .max(3.0)
-        .onChange(function (value) {
-            material.displacementScale = value;
-        });
-
-    gui.add(settings, 'normalScale')
-        .min(-1)
-        .max(1)
-        .onChange(function (value) {
-            material.normalScale.set(1, -1).multiplyScalar(value);
-        });
-}
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    scene = new THREE.Scene();
-
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera(-height * aspect, height * aspect, height, -height, 1, 10000);
-    camera.position.z = 1500;
-    scene.add(camera);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enableDamping = true;
-
-    // lights
-
-    ambientLight = new THREE.AmbientLight(0xffffff, settings.ambientIntensity);
-    scene.add(ambientLight);
-
-    pointLight = new THREE.PointLight(0xff0000, 1.5, 0, 0);
-    pointLight.position.z = 2500;
-    scene.add(pointLight);
-
-    const pointLight2 = new THREE.PointLight(0xff6666, 3, 0, 0);
-    camera.add(pointLight2);
-
-    const pointLight3 = new THREE.PointLight(0x0000ff, 1.5, 0, 0);
-    pointLight3.position.x = -1000;
-    pointLight3.position.z = 1000;
-    scene.add(pointLight3);
-
-    // env map
-
-    const path = 'textures/cube/SwedishRoyalCastle/';
-    const format = '.jpg';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
-
-    // textures
-
-    const textureLoader = new THREE.TextureLoader();
-    const normalMap = textureLoader.load('models/obj/ninja/normal.png');
-    const aoMap = textureLoader.load('models/obj/ninja/ao.jpg');
-    const displacementMap = textureLoader.load('models/obj/ninja/displacement.jpg');
-
-    // material
-
-    material = new THREE.MeshStandardMaterial({
-        color: 0xc1c1c1,
-        roughness: settings.roughness,
-        metalness: settings.metalness,
-
-        normalMap: normalMap,
-        normalScale: new THREE.Vector2(1, -1), // why does the normal map require negation in this case?
-
-        aoMap: aoMap,
-        aoMapIntensity: 1,
-
-        displacementMap: displacementMap,
-        displacementScale: settings.displacementScale,
-        displacementBias: -0.428408, // from original model
-
-        envMap: reflectionCube,
-        envMapIntensity: settings.envMapIntensity,
-
-        side: THREE.DoubleSide,
-    });
-
-    //
-
-    const loader = new OBJLoader();
-    loader.load('models/obj/ninja/ninjaHead_Low.obj', function (group) {
-        const geometry = group.children[0].geometry;
-        geometry.center();
-
-        mesh = new THREE.Mesh(geometry, material);
-        mesh.scale.multiplyScalar(25);
-        scene.add(mesh);
-    });
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera.left = -height * aspect;
-    camera.right = height * aspect;
-    camera.top = height;
-    camera.bottom = -height;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    controls.update();
-
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    pointLight.position.x = 2500 * Math.cos(r);
-    pointLight.position.z = 2500 * Math.sin(r);
-
-    r += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_envmaps.ts b/examples-testing/examples/webgl_materials_envmaps.ts
deleted file mode 100644
index 18a5542ed..000000000
--- a/examples-testing/examples/webgl_materials_envmaps.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let controls, camera, scene, renderer;
-let textureEquirec, textureCube;
-let sphereMesh, sphereMaterial, params;
-
-init();
-
-function init() {
-    // CAMERAS
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 2.5);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    // Textures
-
-    const loader = new THREE.CubeTextureLoader();
-    loader.setPath('textures/cube/Bridge2/');
-
-    textureCube = loader.load(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
-
-    const textureLoader = new THREE.TextureLoader();
-
-    textureEquirec = textureLoader.load('textures/2294472375_24a3b8ef46_o.jpg');
-    textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
-    textureEquirec.colorSpace = THREE.SRGBColorSpace;
-
-    scene.background = textureCube;
-
-    //
-
-    const geometry = new THREE.IcosahedronGeometry(1, 15);
-    sphereMaterial = new THREE.MeshBasicMaterial({ envMap: textureCube });
-    sphereMesh = new THREE.Mesh(geometry, sphereMaterial);
-    scene.add(sphereMesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1.5;
-    controls.maxDistance = 6;
-
-    //
-
-    params = {
-        Cube: function () {
-            scene.background = textureCube;
-
-            sphereMaterial.envMap = textureCube;
-            sphereMaterial.needsUpdate = true;
-        },
-        Equirectangular: function () {
-            scene.background = textureEquirec;
-
-            sphereMaterial.envMap = textureEquirec;
-            sphereMaterial.needsUpdate = true;
-        },
-        Refraction: false,
-        backgroundRotationX: false,
-        backgroundRotationY: false,
-        backgroundRotationZ: false,
-        syncMaterial: false,
-    };
-
-    const gui = new GUI({ width: 300 });
-    gui.add(params, 'Cube');
-    gui.add(params, 'Equirectangular');
-    gui.add(params, 'Refraction').onChange(function (value) {
-        if (value) {
-            textureEquirec.mapping = THREE.EquirectangularRefractionMapping;
-            textureCube.mapping = THREE.CubeRefractionMapping;
-        } else {
-            textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
-            textureCube.mapping = THREE.CubeReflectionMapping;
-        }
-
-        sphereMaterial.needsUpdate = true;
-    });
-    gui.add(params, 'backgroundRotationX');
-    gui.add(params, 'backgroundRotationY');
-    gui.add(params, 'backgroundRotationZ');
-    gui.add(params, 'syncMaterial');
-    gui.open();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    if (params.backgroundRotationX) {
-        scene.backgroundRotation.x += 0.001;
-    }
-
-    if (params.backgroundRotationY) {
-        scene.backgroundRotation.y += 0.001;
-    }
-
-    if (params.backgroundRotationZ) {
-        scene.backgroundRotation.z += 0.001;
-    }
-
-    if (params.syncMaterial) {
-        sphereMesh.material.envMapRotation.copy(scene.backgroundRotation);
-    }
-
-    camera.lookAt(scene.position);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_envmaps_exr.ts b/examples-testing/examples/webgl_materials_envmaps_exr.ts
deleted file mode 100644
index c3f3f4f7d..000000000
--- a/examples-testing/examples/webgl_materials_envmaps_exr.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
-
-const params = {
-    envMap: 'EXR',
-    roughness: 0.0,
-    metalness: 0.0,
-    exposure: 1.0,
-    debug: false,
-};
-
-let container, stats;
-let camera, scene, renderer, controls;
-let torusMesh, planeMesh;
-let pngCubeRenderTarget, exrCubeRenderTarget;
-let pngBackground, exrBackground;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 120);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-
-    //
-
-    let geometry = new THREE.TorusKnotGeometry(18, 8, 150, 20);
-    let material = new THREE.MeshStandardMaterial({
-        metalness: params.metalness,
-        roughness: params.roughness,
-        envMapIntensity: 1.0,
-    });
-
-    torusMesh = new THREE.Mesh(geometry, material);
-    scene.add(torusMesh);
-
-    geometry = new THREE.PlaneGeometry(200, 200);
-    material = new THREE.MeshBasicMaterial();
-
-    planeMesh = new THREE.Mesh(geometry, material);
-    planeMesh.position.y = -50;
-    planeMesh.rotation.x = -Math.PI * 0.5;
-    scene.add(planeMesh);
-
-    THREE.DefaultLoadingManager.onLoad = function () {
-        pmremGenerator.dispose();
-    };
-
-    new EXRLoader().load('textures/piz_compressed.exr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        exrCubeRenderTarget = pmremGenerator.fromEquirectangular(texture);
-        exrBackground = texture;
-    });
-
-    new THREE.TextureLoader().load('textures/equirectangular.png', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        pngCubeRenderTarget = pmremGenerator.fromEquirectangular(texture);
-        pngBackground = texture;
-    });
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    pmremGenerator.compileEquirectangularShader();
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 50;
-    controls.maxDistance = 300;
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'envMap', ['EXR', 'PNG']);
-    gui.add(params, 'roughness', 0, 1, 0.01);
-    gui.add(params, 'metalness', 0, 1, 0.01);
-    gui.add(params, 'exposure', 0, 2, 0.01);
-    gui.add(params, 'debug');
-    gui.open();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    torusMesh.material.roughness = params.roughness;
-    torusMesh.material.metalness = params.metalness;
-
-    let newEnvMap = torusMesh.material.envMap;
-    let background = scene.background;
-
-    switch (params.envMap) {
-        case 'EXR':
-            newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
-            background = exrBackground;
-            break;
-        case 'PNG':
-            newEnvMap = pngCubeRenderTarget ? pngCubeRenderTarget.texture : null;
-            background = pngBackground;
-            break;
-    }
-
-    if (newEnvMap !== torusMesh.material.envMap) {
-        torusMesh.material.envMap = newEnvMap;
-        torusMesh.material.needsUpdate = true;
-
-        planeMesh.material.map = newEnvMap;
-        planeMesh.material.needsUpdate = true;
-    }
-
-    torusMesh.rotation.y += 0.005;
-    planeMesh.visible = params.debug;
-
-    scene.background = background;
-    renderer.toneMappingExposure = params.exposure;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts b/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
deleted file mode 100644
index 48e0077f4..000000000
--- a/examples-testing/examples/webgl_materials_envmaps_groundprojected.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-const params = {
-    height: 15,
-    radius: 100,
-    enabled: true,
-};
-
-let camera, scene, renderer, skybox;
-
-init().then(render);
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(-20, 7, 20);
-    camera.lookAt(0, 4, 0);
-
-    scene = new THREE.Scene();
-
-    const hdrLoader = new RGBELoader();
-    const envMap = await hdrLoader.loadAsync('textures/equirectangular/blouberg_sunrise_2_1k.hdr');
-    envMap.mapping = THREE.EquirectangularReflectionMapping;
-
-    skybox = new GroundedSkybox(envMap, params.height, params.radius);
-    skybox.position.y = params.height - 0.01;
-    scene.add(skybox);
-
-    scene.environment = envMap;
-
-    const dracoLoader = new DRACOLoader();
-    dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
-
-    const loader = new GLTFLoader();
-    loader.setDRACOLoader(dracoLoader);
-
-    const shadow = new THREE.TextureLoader().load('models/gltf/ferrari_ao.png');
-
-    loader.load('models/gltf/ferrari.glb', function (gltf) {
-        const bodyMaterial = new THREE.MeshPhysicalMaterial({
-            color: 0x000000,
-            metalness: 1.0,
-            roughness: 0.8,
-            clearcoat: 1.0,
-            clearcoatRoughness: 0.2,
-        });
-
-        const detailsMaterial = new THREE.MeshStandardMaterial({
-            color: 0xffffff,
-            metalness: 1.0,
-            roughness: 0.5,
-        });
-
-        const glassMaterial = new THREE.MeshPhysicalMaterial({
-            color: 0xffffff,
-            metalness: 0.25,
-            roughness: 0,
-            transmission: 1.0,
-        });
-
-        const carModel = gltf.scene.children[0];
-        carModel.scale.multiplyScalar(4);
-        carModel.rotation.y = Math.PI;
-
-        carModel.getObjectByName('body').material = bodyMaterial;
-
-        carModel.getObjectByName('rim_fl').material = detailsMaterial;
-        carModel.getObjectByName('rim_fr').material = detailsMaterial;
-        carModel.getObjectByName('rim_rr').material = detailsMaterial;
-        carModel.getObjectByName('rim_rl').material = detailsMaterial;
-        carModel.getObjectByName('trim').material = detailsMaterial;
-
-        carModel.getObjectByName('glass').material = glassMaterial;
-
-        // shadow
-        const mesh = new THREE.Mesh(
-            new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4),
-            new THREE.MeshBasicMaterial({
-                map: shadow,
-                blending: THREE.MultiplyBlending,
-                toneMapped: false,
-                transparent: true,
-            }),
-        );
-        mesh.rotation.x = -Math.PI / 2;
-        carModel.add(mesh);
-
-        scene.add(carModel);
-
-        render();
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.target.set(0, 2, 0);
-    controls.maxPolarAngle = THREE.MathUtils.degToRad(90);
-    controls.maxDistance = 80;
-    controls.minDistance = 20;
-    controls.enablePan = false;
-    controls.update();
-
-    document.body.appendChild(renderer.domElement);
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'enabled')
-        .name('Grounded')
-        .onChange(function (value) {
-            if (value) {
-                scene.add(skybox);
-                scene.background = null;
-            } else {
-                scene.remove(skybox);
-                scene.background = scene.environment;
-            }
-
-            render();
-        });
-
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_envmaps_hdr.ts b/examples-testing/examples/webgl_materials_envmaps_hdr.ts
deleted file mode 100644
index b4c6f64ef..000000000
--- a/examples-testing/examples/webgl_materials_envmaps_hdr.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
-import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
-import { DebugEnvironment } from 'three/addons/environments/DebugEnvironment.js';
-
-const params = {
-    envMap: 'HDR',
-    roughness: 0.0,
-    metalness: 0.0,
-    exposure: 1.0,
-    debug: false,
-};
-
-let container, stats;
-let camera, scene, renderer, controls;
-let torusMesh, planeMesh;
-let generatedCubeRenderTarget, ldrCubeRenderTarget, hdrCubeRenderTarget, rgbmCubeRenderTarget;
-let ldrCubeMap, hdrCubeMap, rgbmCubeMap;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 120);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x000000);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-
-    //
-
-    let geometry = new THREE.TorusKnotGeometry(18, 8, 150, 20);
-    // let geometry = new THREE.SphereGeometry( 26, 64, 32 );
-    let material = new THREE.MeshStandardMaterial({
-        color: 0xffffff,
-        metalness: params.metalness,
-        roughness: params.roughness,
-    });
-
-    torusMesh = new THREE.Mesh(geometry, material);
-    scene.add(torusMesh);
-
-    geometry = new THREE.PlaneGeometry(200, 200);
-    material = new THREE.MeshBasicMaterial();
-
-    planeMesh = new THREE.Mesh(geometry, material);
-    planeMesh.position.y = -50;
-    planeMesh.rotation.x = -Math.PI * 0.5;
-    scene.add(planeMesh);
-
-    THREE.DefaultLoadingManager.onLoad = function () {
-        pmremGenerator.dispose();
-    };
-
-    const hdrUrls = ['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'];
-    hdrCubeMap = new HDRCubeTextureLoader().setPath('./textures/cube/pisaHDR/').load(hdrUrls, function () {
-        hdrCubeRenderTarget = pmremGenerator.fromCubemap(hdrCubeMap);
-
-        hdrCubeMap.magFilter = THREE.LinearFilter;
-        hdrCubeMap.needsUpdate = true;
-    });
-
-    const ldrUrls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
-    ldrCubeMap = new THREE.CubeTextureLoader().setPath('./textures/cube/pisa/').load(ldrUrls, function () {
-        ldrCubeRenderTarget = pmremGenerator.fromCubemap(ldrCubeMap);
-    });
-
-    const rgbmUrls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];
-    rgbmCubeMap = new RGBMLoader()
-        .setMaxRange(16)
-        .setPath('./textures/cube/pisaRGBM16/')
-        .loadCubemap(rgbmUrls, function () {
-            rgbmCubeRenderTarget = pmremGenerator.fromCubemap(rgbmCubeMap);
-        });
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    pmremGenerator.compileCubemapShader();
-
-    const envScene = new DebugEnvironment();
-    generatedCubeRenderTarget = pmremGenerator.fromScene(envScene);
-
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //renderer.toneMapping = ReinhardToneMapping;
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 50;
-    controls.maxDistance = 300;
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    gui.add(params, 'envMap', ['Generated', 'LDR', 'HDR', 'RGBM16']);
-    gui.add(params, 'roughness', 0, 1, 0.01);
-    gui.add(params, 'metalness', 0, 1, 0.01);
-    gui.add(params, 'exposure', 0, 2, 0.01);
-    gui.add(params, 'debug');
-    gui.open();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    torusMesh.material.roughness = params.roughness;
-    torusMesh.material.metalness = params.metalness;
-
-    let renderTarget, cubeMap;
-
-    switch (params.envMap) {
-        case 'Generated':
-            renderTarget = generatedCubeRenderTarget;
-            cubeMap = generatedCubeRenderTarget.texture;
-            break;
-        case 'LDR':
-            renderTarget = ldrCubeRenderTarget;
-            cubeMap = ldrCubeMap;
-            break;
-        case 'HDR':
-            renderTarget = hdrCubeRenderTarget;
-            cubeMap = hdrCubeMap;
-            break;
-        case 'RGBM16':
-            renderTarget = rgbmCubeRenderTarget;
-            cubeMap = rgbmCubeMap;
-            break;
-    }
-
-    const newEnvMap = renderTarget ? renderTarget.texture : null;
-
-    if (newEnvMap && newEnvMap !== torusMesh.material.envMap) {
-        torusMesh.material.envMap = newEnvMap;
-        torusMesh.material.needsUpdate = true;
-
-        planeMesh.material.map = newEnvMap;
-        planeMesh.material.needsUpdate = true;
-    }
-
-    torusMesh.rotation.y += 0.005;
-    planeMesh.visible = params.debug;
-
-    scene.background = cubeMap;
-    renderer.toneMappingExposure = params.exposure;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_modified.ts b/examples-testing/examples/webgl_materials_modified.ts
deleted file mode 100644
index c6ee5af3c..000000000
--- a/examples-testing/examples/webgl_materials_modified.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 20;
-
-    scene = new THREE.Scene();
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        const geometry = gltf.scene.children[0].geometry;
-
-        let mesh = new THREE.Mesh(geometry, buildTwistMaterial(2.0));
-        mesh.position.x = -3.5;
-        mesh.position.y = -0.5;
-        scene.add(mesh);
-
-        mesh = new THREE.Mesh(geometry, buildTwistMaterial(-2.0));
-        mesh.position.x = 3.5;
-        mesh.position.y = -0.5;
-        scene.add(mesh);
-    });
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 10;
-    controls.maxDistance = 50;
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // EVENTS
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function buildTwistMaterial(amount) {
-    const material = new THREE.MeshNormalMaterial();
-    material.onBeforeCompile = function (shader) {
-        shader.uniforms.time = { value: 0 };
-
-        shader.vertexShader = 'uniform float time;\n' + shader.vertexShader;
-        shader.vertexShader = shader.vertexShader.replace(
-            '#include <begin_vertex>',
-            [
-                `float theta = sin( time + position.y ) / ${amount.toFixed(1)};`,
-                'float c = cos( theta );',
-                'float s = sin( theta );',
-                'mat3 m = mat3( c, 0, s, 0, 1, 0, -s, 0, c );',
-                'vec3 transformed = vec3( position ) * m;',
-                'vNormal = vNormal * m;',
-            ].join('\n'),
-        );
-
-        material.userData.shader = shader;
-    };
-
-    // Make sure WebGLRenderer doesnt reuse a single program
-
-    material.customProgramCacheKey = function () {
-        return amount.toFixed(1);
-    };
-
-    return material;
-}
-
-//
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    scene.traverse(function (child) {
-        if (child.isMesh) {
-            const shader = child.material.userData.shader;
-
-            if (shader) {
-                shader.uniforms.time.value = performance.now() / 1000;
-            }
-        }
-    });
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_normalmap_object_space.ts b/examples-testing/examples/webgl_materials_normalmap_object_space.ts
deleted file mode 100644
index 1fc6f8066..000000000
--- a/examples-testing/examples/webgl_materials_normalmap_object_space.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let renderer, scene, camera;
-
-init();
-
-function init() {
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(-10, 0, 23);
-    scene.add(camera);
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 10;
-    controls.maxDistance = 50;
-    controls.enablePan = false;
-
-    // ambient
-    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
-
-    // light
-    const light = new THREE.PointLight(0xffffff, 4.5, 0, 0);
-    camera.add(light);
-
-    // model
-    new GLTFLoader().load('models/gltf/Nefertiti/Nefertiti.glb', function (gltf) {
-        gltf.scene.traverse(function (child) {
-            if (child.isMesh) {
-                // glTF currently supports only tangent-space normal maps.
-                // this model has been modified to demonstrate the use of an object-space normal map.
-
-                child.material.normalMapType = THREE.ObjectSpaceNormalMap;
-
-                // attribute normals are not required with an object-space normal map. remove them.
-
-                child.geometry.deleteAttribute('normal');
-
-                //
-
-                child.material.side = THREE.DoubleSide;
-
-                child.scale.multiplyScalar(0.5);
-
-                // recenter
-
-                new THREE.Box3().setFromObject(child).getCenter(child.position).multiplyScalar(-1);
-
-                scene.add(child);
-            }
-        });
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_physical_clearcoat.ts b/examples-testing/examples/webgl_materials_physical_clearcoat.ts
deleted file mode 100644
index 408fd9921..000000000
--- a/examples-testing/examples/webgl_materials_physical_clearcoat.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
-
-import { FlakesTexture } from 'three/addons/textures/FlakesTexture.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let particleLight;
-let group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.25, 50);
-    camera.position.z = 10;
-
-    scene = new THREE.Scene();
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    new HDRCubeTextureLoader()
-        .setPath('textures/cube/pisaHDR/')
-        .load(['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'], function (texture) {
-            const geometry = new THREE.SphereGeometry(0.8, 64, 32);
-
-            const textureLoader = new THREE.TextureLoader();
-
-            const diffuse = textureLoader.load('textures/carbon/Carbon.png');
-            diffuse.colorSpace = THREE.SRGBColorSpace;
-            diffuse.wrapS = THREE.RepeatWrapping;
-            diffuse.wrapT = THREE.RepeatWrapping;
-            diffuse.repeat.x = 10;
-            diffuse.repeat.y = 10;
-
-            const normalMap = textureLoader.load('textures/carbon/Carbon_Normal.png');
-            normalMap.wrapS = THREE.RepeatWrapping;
-            normalMap.wrapT = THREE.RepeatWrapping;
-            normalMap.repeat.x = 10;
-            normalMap.repeat.y = 10;
-
-            const normalMap2 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
-
-            const normalMap3 = new THREE.CanvasTexture(new FlakesTexture());
-            normalMap3.wrapS = THREE.RepeatWrapping;
-            normalMap3.wrapT = THREE.RepeatWrapping;
-            normalMap3.repeat.x = 10;
-            normalMap3.repeat.y = 6;
-            normalMap3.anisotropy = 16;
-
-            const normalMap4 = textureLoader.load('textures/golfball.jpg');
-
-            const clearcoatNormalMap = textureLoader.load(
-                'textures/pbr/Scratched_gold/Scratched_gold_01_1K_Normal.png',
-            );
-
-            // car paint
-
-            let material = new THREE.MeshPhysicalMaterial({
-                clearcoat: 1.0,
-                clearcoatRoughness: 0.1,
-                metalness: 0.9,
-                roughness: 0.5,
-                color: 0x0000ff,
-                normalMap: normalMap3,
-                normalScale: new THREE.Vector2(0.15, 0.15),
-            });
-
-            let mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = -1;
-            mesh.position.y = 1;
-            group.add(mesh);
-
-            // fibers
-
-            material = new THREE.MeshPhysicalMaterial({
-                roughness: 0.5,
-                clearcoat: 1.0,
-                clearcoatRoughness: 0.1,
-                map: diffuse,
-                normalMap: normalMap,
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = 1;
-            mesh.position.y = 1;
-            group.add(mesh);
-
-            // golf
-
-            material = new THREE.MeshPhysicalMaterial({
-                metalness: 0.0,
-                roughness: 0.1,
-                clearcoat: 1.0,
-                normalMap: normalMap4,
-                clearcoatNormalMap: clearcoatNormalMap,
-
-                // y scale is negated to compensate for normal map handedness.
-                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = -1;
-            mesh.position.y = -1;
-            group.add(mesh);
-
-            // clearcoat + normalmap
-
-            material = new THREE.MeshPhysicalMaterial({
-                clearcoat: 1.0,
-                metalness: 1.0,
-                color: 0xff0000,
-                normalMap: normalMap2,
-                normalScale: new THREE.Vector2(0.15, 0.15),
-                clearcoatNormalMap: clearcoatNormalMap,
-
-                // y scale is negated to compensate for normal map handedness.
-                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = 1;
-            mesh.position.y = -1;
-            group.add(mesh);
-
-            //
-
-            scene.background = texture;
-            scene.environment = texture;
-        });
-
-    // LIGHTS
-
-    particleLight = new THREE.Mesh(
-        new THREE.SphereGeometry(0.05, 8, 8),
-        new THREE.MeshBasicMaterial({ color: 0xffffff }),
-    );
-    scene.add(particleLight);
-
-    particleLight.add(new THREE.PointLight(0xffffff, 30));
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1.25;
-
-    //
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // EVENTS
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 3;
-    controls.maxDistance = 30;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const timer = Date.now() * 0.00025;
-
-    particleLight.position.x = Math.sin(timer * 7) * 3;
-    particleLight.position.y = Math.cos(timer * 5) * 4;
-    particleLight.position.z = Math.cos(timer * 3) * 3;
-
-    for (let i = 0; i < group.children.length; i++) {
-        const child = group.children[i];
-        child.rotation.y += 0.005;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_physical_transmission.ts b/examples-testing/examples/webgl_materials_physical_transmission.ts
deleted file mode 100644
index d45967971..000000000
--- a/examples-testing/examples/webgl_materials_physical_transmission.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-const params = {
-    color: 0xffffff,
-    transmission: 1,
-    opacity: 1,
-    metalness: 0,
-    roughness: 0,
-    ior: 1.5,
-    thickness: 0.01,
-    specularIntensity: 1,
-    specularColor: 0xffffff,
-    envMapIntensity: 1,
-    lightIntensity: 1,
-    exposure: 1,
-};
-
-let camera, scene, renderer;
-
-let mesh;
-
-const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
-    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
-
-    init();
-    render();
-});
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = params.exposure;
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(0, 0, 120);
-
-    //
-
-    scene.background = hdrEquirect;
-
-    //
-
-    const geometry = new THREE.SphereGeometry(20, 64, 32);
-
-    const texture = new THREE.CanvasTexture(generateTexture());
-    texture.magFilter = THREE.NearestFilter;
-    texture.wrapT = THREE.RepeatWrapping;
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.repeat.set(1, 3.5);
-
-    const material = new THREE.MeshPhysicalMaterial({
-        color: params.color,
-        metalness: params.metalness,
-        roughness: params.roughness,
-        ior: params.ior,
-        alphaMap: texture,
-        envMap: hdrEquirect,
-        envMapIntensity: params.envMapIntensity,
-        transmission: params.transmission, // use material.transmission for glass materials
-        specularIntensity: params.specularIntensity,
-        specularColor: params.specularColor,
-        opacity: params.opacity,
-        side: THREE.DoubleSide,
-        transparent: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 10;
-    controls.maxDistance = 150;
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    gui.addColor(params, 'color').onChange(function () {
-        material.color.set(params.color);
-        render();
-    });
-
-    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
-        material.transmission = params.transmission;
-        render();
-    });
-
-    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
-        material.opacity = params.opacity;
-        render();
-    });
-
-    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
-        material.metalness = params.metalness;
-        render();
-    });
-
-    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
-        material.roughness = params.roughness;
-        render();
-    });
-
-    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
-        material.ior = params.ior;
-        render();
-    });
-
-    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
-        material.thickness = params.thickness;
-        render();
-    });
-
-    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
-        material.specularIntensity = params.specularIntensity;
-        render();
-    });
-
-    gui.addColor(params, 'specularColor').onChange(function () {
-        material.specularColor.set(params.specularColor);
-        render();
-    });
-
-    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
-        .name('envMap intensity')
-        .onChange(function () {
-            material.envMapIntensity = params.envMapIntensity;
-            render();
-        });
-
-    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
-        renderer.toneMappingExposure = params.exposure;
-        render();
-    });
-
-    gui.open();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-
-    render();
-}
-
-//
-
-function generateTexture() {
-    const canvas = document.createElement('canvas');
-    canvas.width = 2;
-    canvas.height = 2;
-
-    const context = canvas.getContext('2d');
-    context.fillStyle = 'white';
-    context.fillRect(0, 1, 2, 1);
-
-    return canvas;
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts b/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
deleted file mode 100644
index d81f59c37..000000000
--- a/examples-testing/examples/webgl_materials_physical_transmission_alpha.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-const params = {
-    color: 0xffffff,
-    transmission: 1,
-    opacity: 1,
-    metalness: 0,
-    roughness: 0,
-    ior: 1.5,
-    thickness: 0.01,
-    attenuationColor: 0xffffff,
-    attenuationDistance: 1,
-    specularIntensity: 1,
-    specularColor: 0xffffff,
-    envMapIntensity: 1,
-    lightIntensity: 1,
-    exposure: 1,
-};
-
-let camera, scene, renderer;
-
-let mesh, material;
-
-const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
-    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
-
-    new GLTFLoader().setPath('models/gltf/').load('DragonAttenuation.glb', function (gltf) {
-        gltf.scene.traverse(function (child) {
-            if (child.isMesh && child.material.isMeshPhysicalMaterial) {
-                mesh = child;
-                material = mesh.material;
-
-                const color = new THREE.Color();
-
-                params.color = color.copy(mesh.material.color).getHex();
-                params.roughness = mesh.material.roughness;
-                params.metalness = mesh.material.metalness;
-
-                params.ior = mesh.material.ior;
-                params.specularIntensity = mesh.material.specularIntensity;
-
-                params.transmission = mesh.material.transmission;
-                params.thickness = mesh.material.thickness;
-                params.attenuationColor = color.copy(mesh.material.attenuationColor).getHex();
-                params.attenuationDistance = mesh.material.attenuationDistance;
-            }
-        });
-
-        init();
-
-        scene.add(gltf.scene);
-
-        scene.environment = hdrEquirect;
-        //scene.background = hdrEquirect;
-
-        render();
-    });
-});
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = params.exposure;
-
-    // accommodate CSS table
-    renderer.domElement.style.position = 'absolute';
-    renderer.domElement.style.top = 0;
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(-5, 0.5, 0);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 5;
-    controls.maxDistance = 20;
-    controls.target.y = 0.5;
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    gui.addColor(params, 'color').onChange(function () {
-        material.color.set(params.color);
-        render();
-    });
-
-    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
-        material.transmission = params.transmission;
-        render();
-    });
-
-    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
-        material.opacity = params.opacity;
-        const transparent = params.opacity < 1;
-
-        if (transparent !== material.transparent) {
-            material.transparent = transparent;
-            material.needsUpdate = true;
-        }
-
-        render();
-    });
-
-    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
-        material.metalness = params.metalness;
-        render();
-    });
-
-    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
-        material.roughness = params.roughness;
-        render();
-    });
-
-    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
-        material.ior = params.ior;
-        render();
-    });
-
-    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
-        material.thickness = params.thickness;
-        render();
-    });
-
-    gui.addColor(params, 'attenuationColor')
-        .name('attenuation color')
-        .onChange(function () {
-            material.attenuationColor.set(params.attenuationColor);
-            render();
-        });
-
-    gui.add(params, 'attenuationDistance', 0, 1, 0.01).onChange(function () {
-        material.attenuationDistance = params.attenuationDistance;
-        render();
-    });
-
-    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
-        material.specularIntensity = params.specularIntensity;
-        render();
-    });
-
-    gui.addColor(params, 'specularColor').onChange(function () {
-        material.specularColor.set(params.specularColor);
-        render();
-    });
-
-    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
-        .name('envMap intensity')
-        .onChange(function () {
-            material.envMapIntensity = params.envMapIntensity;
-            render();
-        });
-
-    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
-        renderer.toneMappingExposure = params.exposure;
-        render();
-    });
-
-    gui.open();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_texture_anisotropy.ts b/examples-testing/examples/webgl_materials_texture_anisotropy.ts
deleted file mode 100644
index 1e030d64d..000000000
--- a/examples-testing/examples/webgl_materials_texture_anisotropy.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-const SCREEN_WIDTH = window.innerWidth;
-const SCREEN_HEIGHT = window.innerHeight;
-
-let container, stats;
-
-let camera, scene1, scene2, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-
-    //
-
-    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 25000);
-    camera.position.z = 1500;
-
-    scene1 = new THREE.Scene();
-    scene1.background = new THREE.Color(0xf2f7ff);
-    scene1.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
-
-    scene2 = new THREE.Scene();
-    scene2.background = new THREE.Color(0xf2f7ff);
-    scene2.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
-
-    scene1.add(new THREE.AmbientLight(0xeef0ff, 3));
-    scene2.add(new THREE.AmbientLight(0xeef0ff, 3));
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 6);
-    light1.position.set(1, 1, 1);
-    scene1.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 6);
-    light2.position.set(1, 1, 1);
-    scene2.add(light2);
-
-    // GROUND
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
-
-    const texture1 = textureLoader.load('textures/crate.gif');
-    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
-
-    texture1.colorSpace = THREE.SRGBColorSpace;
-    texture1.anisotropy = maxAnisotropy;
-    texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
-    texture1.repeat.set(512, 512);
-
-    const texture2 = textureLoader.load('textures/crate.gif');
-    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
-
-    texture2.colorSpace = THREE.SRGBColorSpace;
-    texture2.anisotropy = 1;
-    texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping;
-    texture2.repeat.set(512, 512);
-
-    if (maxAnisotropy > 0) {
-        document.getElementById('val_left').innerHTML = texture1.anisotropy;
-        document.getElementById('val_right').innerHTML = texture2.anisotropy;
-    } else {
-        document.getElementById('val_left').innerHTML = 'not supported';
-        document.getElementById('val_right').innerHTML = 'not supported';
-    }
-
-    //
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-
-    const mesh1 = new THREE.Mesh(geometry, material1);
-    mesh1.rotation.x = -Math.PI / 2;
-    mesh1.scale.set(1000, 1000, 1000);
-
-    const mesh2 = new THREE.Mesh(geometry, material2);
-    mesh2.rotation.x = -Math.PI / 2;
-    mesh2.scale.set(1000, 1000, 1000);
-
-    scene1.add(mesh1);
-    scene2.add(mesh2);
-
-    // RENDERER
-
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    renderer.domElement.style.position = 'relative';
-    container.appendChild(renderer.domElement);
-
-    // STATS1
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y = THREE.MathUtils.clamp(
-        camera.position.y + (-(mouseY - 200) - camera.position.y) * 0.05,
-        50,
-        1000,
-    );
-
-    camera.lookAt(scene1.position);
-
-    renderer.clear();
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene1, camera);
-
-    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene2, camera);
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgl_materials_texture_canvas.ts b/examples-testing/examples/webgl_materials_texture_canvas.ts
deleted file mode 100644
index d23c68436..000000000
--- a/examples-testing/examples/webgl_materials_texture_canvas.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, mesh, material;
-const drawStartPos = new THREE.Vector2();
-
-init();
-setupCanvasDrawing();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    material = new THREE.MeshBasicMaterial();
-
-    mesh = new THREE.Mesh(new THREE.BoxGeometry(200, 200, 200), material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-// Sets up the drawing canvas and adds it as the material map
-
-function setupCanvasDrawing() {
-    // get canvas and context
-
-    const drawingCanvas = document.getElementById('drawing-canvas');
-    const drawingContext = drawingCanvas.getContext('2d');
-
-    // draw white background
-
-    drawingContext.fillStyle = '#FFFFFF';
-    drawingContext.fillRect(0, 0, 128, 128);
-
-    // set canvas as material.map (this could be done to any map, bump, displacement etc.)
-
-    material.map = new THREE.CanvasTexture(drawingCanvas);
-
-    // set the variable to keep track of when to draw
-
-    let paint = false;
-
-    // add canvas event listeners
-    drawingCanvas.addEventListener('pointerdown', function (e) {
-        paint = true;
-        drawStartPos.set(e.offsetX, e.offsetY);
-    });
-
-    drawingCanvas.addEventListener('pointermove', function (e) {
-        if (paint) draw(drawingContext, e.offsetX, e.offsetY);
-    });
-
-    drawingCanvas.addEventListener('pointerup', function () {
-        paint = false;
-    });
-
-    drawingCanvas.addEventListener('pointerleave', function () {
-        paint = false;
-    });
-}
-
-function draw(drawContext, x, y) {
-    drawContext.moveTo(drawStartPos.x, drawStartPos.y);
-    drawContext.strokeStyle = '#000000';
-    drawContext.lineTo(x, y);
-    drawContext.stroke();
-    // reset drawing start position to current position.
-    drawStartPos.set(x, y);
-    // need to flag the map as needing updating.
-    material.map.needsUpdate = true;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.rotation.x += 0.01;
-    mesh.rotation.y += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_texture_filters.ts b/examples-testing/examples/webgl_materials_texture_filters.ts
deleted file mode 100644
index 178c2ce49..000000000
--- a/examples-testing/examples/webgl_materials_texture_filters.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import * as THREE from 'three';
-
-const SCREEN_WIDTH = window.innerWidth;
-const SCREEN_HEIGHT = window.innerHeight;
-
-let container;
-
-let camera, scene, scene2, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 5000);
-    camera.position.z = 1500;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x000000);
-    scene.fog = new THREE.Fog(0x000000, 1500, 4000);
-
-    scene2 = new THREE.Scene();
-    scene2.background = new THREE.Color(0x000000);
-    scene2.fog = new THREE.Fog(0x000000, 1500, 4000);
-
-    // GROUND
-
-    const imageCanvas = document.createElement('canvas');
-    const context = imageCanvas.getContext('2d');
-
-    imageCanvas.width = imageCanvas.height = 128;
-
-    context.fillStyle = '#444';
-    context.fillRect(0, 0, 128, 128);
-
-    context.fillStyle = '#fff';
-    context.fillRect(0, 0, 64, 64);
-    context.fillRect(64, 64, 64, 64);
-
-    const textureCanvas = new THREE.CanvasTexture(imageCanvas);
-    textureCanvas.colorSpace = THREE.SRGBColorSpace;
-    textureCanvas.repeat.set(1000, 1000);
-    textureCanvas.wrapS = THREE.RepeatWrapping;
-    textureCanvas.wrapT = THREE.RepeatWrapping;
-
-    const textureCanvas2 = textureCanvas.clone();
-    textureCanvas2.magFilter = THREE.NearestFilter;
-    textureCanvas2.minFilter = THREE.NearestFilter;
-
-    const materialCanvas = new THREE.MeshBasicMaterial({ map: textureCanvas });
-    const materialCanvas2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: textureCanvas2 });
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-
-    const meshCanvas = new THREE.Mesh(geometry, materialCanvas);
-    meshCanvas.rotation.x = -Math.PI / 2;
-    meshCanvas.scale.set(1000, 1000, 1000);
-
-    const meshCanvas2 = new THREE.Mesh(geometry, materialCanvas2);
-    meshCanvas2.rotation.x = -Math.PI / 2;
-    meshCanvas2.scale.set(1000, 1000, 1000);
-
-    // PAINTING
-
-    const callbackPainting = function () {
-        const image = texturePainting.image;
-
-        texturePainting2.image = image;
-        texturePainting2.needsUpdate = true;
-
-        scene.add(meshCanvas);
-        scene2.add(meshCanvas2);
-
-        const geometry = new THREE.PlaneGeometry(100, 100);
-        const mesh = new THREE.Mesh(geometry, materialPainting);
-        const mesh2 = new THREE.Mesh(geometry, materialPainting2);
-
-        addPainting(scene, mesh);
-        addPainting(scene2, mesh2);
-
-        function addPainting(zscene, zmesh) {
-            zmesh.scale.x = image.width / 100;
-            zmesh.scale.y = image.height / 100;
-
-            zscene.add(zmesh);
-
-            const meshFrame = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000 }));
-            meshFrame.position.z = -10.0;
-            meshFrame.scale.x = (1.1 * image.width) / 100;
-            meshFrame.scale.y = (1.1 * image.height) / 100;
-            zscene.add(meshFrame);
-
-            const meshShadow = new THREE.Mesh(
-                geometry,
-                new THREE.MeshBasicMaterial({ color: 0x000000, opacity: 0.75, transparent: true }),
-            );
-            meshShadow.position.y = (-1.1 * image.height) / 2;
-            meshShadow.position.z = (-1.1 * image.height) / 2;
-            meshShadow.rotation.x = -Math.PI / 2;
-            meshShadow.scale.x = (1.1 * image.width) / 100;
-            meshShadow.scale.y = (1.1 * image.height) / 100;
-            zscene.add(meshShadow);
-
-            const floorHeight = (-1.117 * image.height) / 2;
-            meshCanvas.position.y = meshCanvas2.position.y = floorHeight;
-        }
-    };
-
-    const texturePainting = new THREE.TextureLoader().load(
-        'textures/758px-Canestra_di_frutta_(Caravaggio).jpg',
-        callbackPainting,
-    );
-    const texturePainting2 = new THREE.Texture();
-
-    const materialPainting = new THREE.MeshBasicMaterial({ color: 0xffffff, map: texturePainting });
-    const materialPainting2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: texturePainting2 });
-
-    texturePainting.colorSpace = THREE.SRGBColorSpace;
-    texturePainting2.colorSpace = THREE.SRGBColorSpace;
-    texturePainting2.minFilter = texturePainting2.magFilter = THREE.NearestFilter;
-    texturePainting.minFilter = texturePainting.magFilter = THREE.LinearFilter;
-    texturePainting.mapping = THREE.UVMapping;
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    renderer.domElement.style.position = 'relative';
-    container.appendChild(renderer.domElement);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-function animate() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-(mouseY - 200) - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    renderer.clear();
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene, camera);
-
-    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene2, camera);
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgl_materials_texture_manualmipmap.ts b/examples-testing/examples/webgl_materials_texture_manualmipmap.ts
deleted file mode 100644
index 24bd4eb9f..000000000
--- a/examples-testing/examples/webgl_materials_texture_manualmipmap.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import * as THREE from 'three';
-
-const SCREEN_WIDTH = window.innerWidth;
-const SCREEN_HEIGHT = window.innerHeight;
-
-let container;
-
-let camera, scene1, scene2, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 5000);
-    camera.position.z = 1500;
-
-    scene1 = new THREE.Scene();
-    scene1.background = new THREE.Color(0x000000);
-    scene1.fog = new THREE.Fog(0x000000, 1500, 4000);
-
-    scene2 = new THREE.Scene();
-    scene2.background = new THREE.Color(0x000000);
-    scene2.fog = new THREE.Fog(0x000000, 1500, 4000);
-
-    // GROUND
-
-    function mipmap(size, color) {
-        const imageCanvas = document.createElement('canvas');
-        const context = imageCanvas.getContext('2d');
-
-        imageCanvas.width = imageCanvas.height = size;
-
-        context.fillStyle = '#444';
-        context.fillRect(0, 0, size, size);
-
-        context.fillStyle = color;
-        context.fillRect(0, 0, size / 2, size / 2);
-        context.fillRect(size / 2, size / 2, size / 2, size / 2);
-        return imageCanvas;
-    }
-
-    const canvas = mipmap(128, '#f00');
-    const textureCanvas1 = new THREE.CanvasTexture(canvas);
-    textureCanvas1.mipmaps[0] = canvas;
-    textureCanvas1.mipmaps[1] = mipmap(64, '#0f0');
-    textureCanvas1.mipmaps[2] = mipmap(32, '#00f');
-    textureCanvas1.mipmaps[3] = mipmap(16, '#400');
-    textureCanvas1.mipmaps[4] = mipmap(8, '#040');
-    textureCanvas1.mipmaps[5] = mipmap(4, '#004');
-    textureCanvas1.mipmaps[6] = mipmap(2, '#044');
-    textureCanvas1.mipmaps[7] = mipmap(1, '#404');
-    textureCanvas1.colorSpace = THREE.SRGBColorSpace;
-    textureCanvas1.repeat.set(1000, 1000);
-    textureCanvas1.wrapS = THREE.RepeatWrapping;
-    textureCanvas1.wrapT = THREE.RepeatWrapping;
-
-    const textureCanvas2 = textureCanvas1.clone();
-    textureCanvas2.magFilter = THREE.NearestFilter;
-    textureCanvas2.minFilter = THREE.NearestMipmapNearestFilter;
-
-    const materialCanvas1 = new THREE.MeshBasicMaterial({ map: textureCanvas1 });
-    const materialCanvas2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: textureCanvas2 });
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-
-    const meshCanvas1 = new THREE.Mesh(geometry, materialCanvas1);
-    meshCanvas1.rotation.x = -Math.PI / 2;
-    meshCanvas1.scale.set(1000, 1000, 1000);
-
-    const meshCanvas2 = new THREE.Mesh(geometry, materialCanvas2);
-    meshCanvas2.rotation.x = -Math.PI / 2;
-    meshCanvas2.scale.set(1000, 1000, 1000);
-
-    // PAINTING
-
-    const callbackPainting = function () {
-        const image = texturePainting1.image;
-
-        texturePainting2.image = image;
-        texturePainting2.needsUpdate = true;
-
-        scene1.add(meshCanvas1);
-        scene2.add(meshCanvas2);
-
-        const geometry = new THREE.PlaneGeometry(100, 100);
-        const mesh1 = new THREE.Mesh(geometry, materialPainting1);
-        const mesh2 = new THREE.Mesh(geometry, materialPainting2);
-
-        addPainting(scene1, mesh1);
-        addPainting(scene2, mesh2);
-
-        function addPainting(zscene, zmesh) {
-            zmesh.scale.x = image.width / 100;
-            zmesh.scale.y = image.height / 100;
-
-            zscene.add(zmesh);
-
-            const meshFrame = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000 }));
-            meshFrame.position.z = -10.0;
-            meshFrame.scale.x = (1.1 * image.width) / 100;
-            meshFrame.scale.y = (1.1 * image.height) / 100;
-            zscene.add(meshFrame);
-
-            const meshShadow = new THREE.Mesh(
-                geometry,
-                new THREE.MeshBasicMaterial({ color: 0x000000, opacity: 0.75, transparent: true }),
-            );
-            meshShadow.position.y = (-1.1 * image.height) / 2;
-            meshShadow.position.z = (-1.1 * image.height) / 2;
-            meshShadow.rotation.x = -Math.PI / 2;
-            meshShadow.scale.x = (1.1 * image.width) / 100;
-            meshShadow.scale.y = (1.1 * image.height) / 100;
-            zscene.add(meshShadow);
-
-            const floorHeight = (-1.117 * image.height) / 2;
-            meshCanvas1.position.y = meshCanvas2.position.y = floorHeight;
-        }
-    };
-
-    const texturePainting1 = new THREE.TextureLoader().load(
-        'textures/758px-Canestra_di_frutta_(Caravaggio).jpg',
-        callbackPainting,
-    );
-    const texturePainting2 = new THREE.Texture();
-    const materialPainting1 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: texturePainting1 });
-    const materialPainting2 = new THREE.MeshBasicMaterial({ color: 0xffccaa, map: texturePainting2 });
-
-    texturePainting1.colorSpace = THREE.SRGBColorSpace;
-    texturePainting2.colorSpace = THREE.SRGBColorSpace;
-    texturePainting2.minFilter = texturePainting2.magFilter = THREE.NearestFilter;
-    texturePainting1.minFilter = texturePainting1.magFilter = THREE.LinearFilter;
-    texturePainting1.mapping = THREE.UVMapping;
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    renderer.domElement.style.position = 'relative';
-    container.appendChild(renderer.domElement);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-function animate() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-(mouseY - 200) - camera.position.y) * 0.05;
-
-    camera.lookAt(scene1.position);
-
-    renderer.clear();
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene1, camera);
-
-    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene2, camera);
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgl_materials_texture_partialupdate.ts b/examples-testing/examples/webgl_materials_texture_partialupdate.ts
deleted file mode 100644
index 5adfc8e69..000000000
--- a/examples-testing/examples/webgl_materials_texture_partialupdate.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, clock, dataTexture, diffuseMap;
-
-let last = 0;
-const position = new THREE.Vector2();
-const color = new THREE.Color();
-
-init();
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    const loader = new THREE.TextureLoader();
-    diffuseMap = await loader.loadAsync('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
-    diffuseMap.colorSpace = THREE.SRGBColorSpace;
-    diffuseMap.minFilter = THREE.LinearFilter;
-    diffuseMap.generateMipmaps = false;
-
-    const geometry = new THREE.PlaneGeometry(2, 2);
-    const material = new THREE.MeshBasicMaterial({ map: diffuseMap });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const width = 32;
-    const height = 32;
-
-    const data = new Uint8Array(width * height * 4);
-    dataTexture = new THREE.DataTexture(data, width, height);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const elapsedTime = clock.getElapsedTime();
-
-    if (elapsedTime - last > 0.1) {
-        last = elapsedTime;
-
-        position.x = 32 * THREE.MathUtils.randInt(1, 16) - 32;
-        position.y = 32 * THREE.MathUtils.randInt(1, 16) - 32;
-
-        // generate new color data
-
-        updateDataTexture(dataTexture);
-
-        // perform copy from src to dest texture to a random position
-
-        renderer.copyTextureToTexture(dataTexture, diffuseMap, null, position);
-    }
-
-    renderer.render(scene, camera);
-}
-
-function updateDataTexture(texture) {
-    const size = texture.image.width * texture.image.height;
-    const data = texture.image.data;
-
-    // generate a random color and update texture data
-
-    color.setHex(Math.random() * 0xffffff);
-
-    const r = Math.floor(color.r * 255);
-    const g = Math.floor(color.g * 255);
-    const b = Math.floor(color.b * 255);
-
-    for (let i = 0; i < size; i++) {
-        const stride = i * 4;
-
-        data[stride] = r;
-        data[stride + 1] = g;
-        data[stride + 2] = b;
-        data[stride + 3] = 1;
-    }
-}
diff --git a/examples-testing/examples/webgl_materials_texture_rotation.ts b/examples-testing/examples/webgl_materials_texture_rotation.ts
deleted file mode 100644
index 2666d09d6..000000000
--- a/examples-testing/examples/webgl_materials_texture_rotation.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let mesh, renderer, scene, camera;
-
-let gui;
-
-const API = {
-    offsetX: 0,
-    offsetY: 0,
-    repeatX: 0.25,
-    repeatY: 0.25,
-    rotation: Math.PI / 4, // positive is counterclockwise
-    centerX: 0.5,
-    centerY: 0.5,
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(10, 15, 25);
-    scene.add(camera);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.minDistance = 20;
-    controls.maxDistance = 50;
-    controls.maxPolarAngle = Math.PI / 2;
-
-    const geometry = new THREE.BoxGeometry(10, 10, 10);
-
-    new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg', function (texture) {
-        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-        texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
-        texture.colorSpace = THREE.SRGBColorSpace;
-
-        //texture.matrixAutoUpdate = false; // default true; set to false to update texture.matrix manually
-
-        const material = new THREE.MeshBasicMaterial({ map: texture });
-
-        mesh = new THREE.Mesh(geometry, material);
-        scene.add(mesh);
-
-        updateUvTransform();
-
-        initGui();
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function updateUvTransform() {
-    const texture = mesh.material.map;
-
-    if (texture.matrixAutoUpdate === true) {
-        texture.offset.set(API.offsetX, API.offsetY);
-        texture.repeat.set(API.repeatX, API.repeatY);
-        texture.center.set(API.centerX, API.centerY);
-        texture.rotation = API.rotation; // rotation is around center
-    } else {
-        // setting the matrix uv transform directly
-        //texture.matrix.setUvTransform( API.offsetX, API.offsetY, API.repeatX, API.repeatY, API.rotation, API.centerX, API.centerY );
-
-        // another way...
-        texture.matrix
-            .identity()
-            .translate(-API.centerX, -API.centerY)
-            .rotate(API.rotation) // I don't understand how rotation can preceed scale, but it seems to be required...
-            .scale(API.repeatX, API.repeatY)
-            .translate(API.centerX, API.centerY)
-            .translate(API.offsetX, API.offsetY);
-    }
-
-    render();
-}
-
-function initGui() {
-    gui = new GUI();
-
-    gui.add(API, 'offsetX', 0.0, 1.0).name('offset.x').onChange(updateUvTransform);
-    gui.add(API, 'offsetY', 0.0, 1.0).name('offset.y').onChange(updateUvTransform);
-    gui.add(API, 'repeatX', 0.25, 2.0).name('repeat.x').onChange(updateUvTransform);
-    gui.add(API, 'repeatY', 0.25, 2.0).name('repeat.y').onChange(updateUvTransform);
-    gui.add(API, 'rotation', -2.0, 2.0).name('rotation').onChange(updateUvTransform);
-    gui.add(API, 'centerX', 0.0, 1.0).name('center.x').onChange(updateUvTransform);
-    gui.add(API, 'centerY', 0.0, 1.0).name('center.y').onChange(updateUvTransform);
-}
diff --git a/examples-testing/examples/webgl_materials_toon.ts b/examples-testing/examples/webgl_materials_toon.ts
deleted file mode 100644
index 03db286ad..000000000
--- a/examples-testing/examples/webgl_materials_toon.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OutlineEffect } from 'three/addons/effects/OutlineEffect.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-let container, stats;
-
-let camera, scene, renderer, effect;
-let particleLight;
-
-const loader = new FontLoader();
-loader.load('fonts/gentilis_regular.typeface.json', function (font) {
-    init(font);
-});
-
-function init(font) {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2500);
-    camera.position.set(0.0, 400, 400 * 3.5);
-
-    //
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x444488);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // Materials
-
-    const cubeWidth = 400;
-    const numberOfSphersPerSide = 5;
-    const sphereRadius = (cubeWidth / numberOfSphersPerSide) * 0.8 * 0.5;
-    const stepSize = 1.0 / numberOfSphersPerSide;
-
-    const geometry = new THREE.SphereGeometry(sphereRadius, 32, 16);
-
-    for (let alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex++) {
-        const colors = new Uint8Array(alphaIndex + 2);
-
-        for (let c = 0; c <= colors.length; c++) {
-            colors[c] = (c / colors.length) * 256;
-        }
-
-        const gradientMap = new THREE.DataTexture(colors, colors.length, 1, THREE.RedFormat);
-        gradientMap.needsUpdate = true;
-
-        for (let beta = 0; beta <= 1.0; beta += stepSize) {
-            for (let gamma = 0; gamma <= 1.0; gamma += stepSize) {
-                // basic monochromatic energy preservation
-                const diffuseColor = new THREE.Color()
-                    .setHSL(alpha, 0.5, gamma * 0.5 + 0.1)
-                    .multiplyScalar(1 - beta * 0.2);
-
-                const material = new THREE.MeshToonMaterial({
-                    color: diffuseColor,
-                    gradientMap: gradientMap,
-                });
-
-                const mesh = new THREE.Mesh(geometry, material);
-
-                mesh.position.x = alpha * 400 - 200;
-                mesh.position.y = beta * 400 - 200;
-                mesh.position.z = gamma * 400 - 200;
-
-                scene.add(mesh);
-            }
-        }
-    }
-
-    function addLabel(name, location) {
-        const textGeo = new TextGeometry(name, {
-            font: font,
-
-            size: 20,
-            depth: 1,
-            curveSegments: 1,
-        });
-
-        const textMaterial = new THREE.MeshBasicMaterial();
-        const textMesh = new THREE.Mesh(textGeo, textMaterial);
-        textMesh.position.copy(location);
-        scene.add(textMesh);
-    }
-
-    addLabel('-gradientMap', new THREE.Vector3(-350, 0, 0));
-    addLabel('+gradientMap', new THREE.Vector3(350, 0, 0));
-
-    addLabel('-diffuse', new THREE.Vector3(0, 0, -300));
-    addLabel('+diffuse', new THREE.Vector3(0, 0, 300));
-
-    particleLight = new THREE.Mesh(new THREE.SphereGeometry(4, 8, 8), new THREE.MeshBasicMaterial({ color: 0xffffff }));
-    scene.add(particleLight);
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0xc1c1c1, 3));
-
-    const pointLight = new THREE.PointLight(0xffffff, 2, 800, 0);
-    particleLight.add(pointLight);
-
-    //
-
-    effect = new OutlineEffect(renderer);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 200;
-    controls.maxDistance = 2000;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    const timer = Date.now() * 0.00025;
-
-    particleLight.position.x = Math.sin(timer * 7) * 300;
-    particleLight.position.y = Math.cos(timer * 5) * 400;
-    particleLight.position.z = Math.cos(timer * 3) * 300;
-
-    effect.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_video.ts b/examples-testing/examples/webgl_materials_video.ts
deleted file mode 100644
index 4f0d26a18..000000000
--- a/examples-testing/examples/webgl_materials_video.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let container;
-
-let camera, scene, renderer;
-
-let video, texture, material, mesh;
-
-let composer;
-
-let mouseX = 0;
-let mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-let cube_count;
-
-const meshes = [],
-    materials = [],
-    xgrid = 20,
-    ygrid = 10;
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', function () {
-    init();
-});
-
-function init() {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0.5, 1, 1).normalize();
-    scene.add(light);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    video = document.getElementById('video');
-    video.play();
-    video.addEventListener('play', function () {
-        this.currentTime = 3;
-    });
-
-    texture = new THREE.VideoTexture(video);
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    //
-
-    let i, j, ox, oy, geometry;
-
-    const ux = 1 / xgrid;
-    const uy = 1 / ygrid;
-
-    const xsize = 480 / xgrid;
-    const ysize = 204 / ygrid;
-
-    const parameters = { color: 0xffffff, map: texture };
-
-    cube_count = 0;
-
-    for (i = 0; i < xgrid; i++) {
-        for (j = 0; j < ygrid; j++) {
-            ox = i;
-            oy = j;
-
-            geometry = new THREE.BoxGeometry(xsize, ysize, xsize);
-
-            change_uvs(geometry, ux, uy, ox, oy);
-
-            materials[cube_count] = new THREE.MeshLambertMaterial(parameters);
-
-            material = materials[cube_count];
-
-            material.hue = i / xgrid;
-            material.saturation = 1 - j / ygrid;
-
-            material.color.setHSL(material.hue, material.saturation, 0.5);
-
-            mesh = new THREE.Mesh(geometry, material);
-
-            mesh.position.x = (i - xgrid / 2) * xsize;
-            mesh.position.y = (j - ygrid / 2) * ysize;
-            mesh.position.z = 0;
-
-            mesh.scale.x = mesh.scale.y = mesh.scale.z = 1;
-
-            scene.add(mesh);
-
-            mesh.dx = 0.001 * (0.5 - Math.random());
-            mesh.dy = 0.001 * (0.5 - Math.random());
-
-            meshes[cube_count] = mesh;
-
-            cube_count += 1;
-        }
-    }
-
-    renderer.autoClear = false;
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    // postprocessing
-
-    const renderPass = new RenderPass(scene, camera);
-    const bloomPass = new BloomPass(1.3);
-    const outputPass = new OutputPass();
-
-    composer = new EffectComposer(renderer);
-
-    composer.addPass(renderPass);
-    composer.addPass(bloomPass);
-    composer.addPass(outputPass);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function change_uvs(geometry, unitx, unity, offsetx, offsety) {
-    const uvs = geometry.attributes.uv.array;
-
-    for (let i = 0; i < uvs.length; i += 2) {
-        uvs[i] = (uvs[i] + offsetx) * unitx;
-        uvs[i + 1] = (uvs[i + 1] + offsety) * unity;
-    }
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = (event.clientY - windowHalfY) * 0.3;
-}
-
-//
-
-let h,
-    counter = 1;
-
-function animate() {
-    const time = Date.now() * 0.00005;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0; i < cube_count; i++) {
-        material = materials[i];
-
-        h = ((360 * (material.hue + time)) % 360) / 360;
-        material.color.setHSL(h, material.saturation, 0.5);
-    }
-
-    if (counter % 1000 > 200) {
-        for (let i = 0; i < cube_count; i++) {
-            mesh = meshes[i];
-
-            mesh.rotation.x += 10 * mesh.dx;
-            mesh.rotation.y += 10 * mesh.dy;
-
-            mesh.position.x -= 150 * mesh.dx;
-            mesh.position.y += 150 * mesh.dy;
-            mesh.position.z += 300 * mesh.dx;
-        }
-    }
-
-    if (counter % 1000 === 0) {
-        for (let i = 0; i < cube_count; i++) {
-            mesh = meshes[i];
-
-            mesh.dx *= -1;
-            mesh.dy *= -1;
-        }
-    }
-
-    counter++;
-
-    renderer.clear();
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_materials_video_webcam.ts b/examples-testing/examples/webgl_materials_video_webcam.ts
deleted file mode 100644
index cf6f8d50c..000000000
--- a/examples-testing/examples/webgl_materials_video_webcam.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, video;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 0.01;
-
-    scene = new THREE.Scene();
-
-    video = document.getElementById('video');
-
-    const texture = new THREE.VideoTexture(video);
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const geometry = new THREE.PlaneGeometry(16, 9);
-    geometry.scale(0.5, 0.5, 0.5);
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    const count = 128;
-    const radius = 32;
-
-    for (let i = 1, l = count; i <= l; i++) {
-        const phi = Math.acos(-1 + (2 * i) / l);
-        const theta = Math.sqrt(l * Math.PI) * phi;
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.setFromSphericalCoords(radius, phi, theta);
-        mesh.lookAt(camera.position);
-        scene.add(mesh);
-    }
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enablePan = false;
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
-        const constraints = { video: { width: 1280, height: 720, facingMode: 'user' } };
-
-        navigator.mediaDevices
-            .getUserMedia(constraints)
-            .then(function (stream) {
-                // apply the stream to the video element used in the texture
-
-                video.srcObject = stream;
-                video.play();
-            })
-            .catch(function (error) {
-                console.error('Unable to access the camera/webcam.', error);
-            });
-    } else {
-        console.error('MediaDevices interface not available.');
-    }
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_materials_wireframe.ts b/examples-testing/examples/webgl_materials_wireframe.ts
deleted file mode 100644
index 8adbd71d6..000000000
--- a/examples-testing/examples/webgl_materials_wireframe.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const API = {
-    thickness: 1,
-};
-
-let renderer, scene, camera, mesh2;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.z = 200;
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.enablePan = false;
-    controls.enableZoom = false;
-
-    new THREE.BufferGeometryLoader().load('models/json/WaltHeadLo_buffergeometry.json', function (geometry) {
-        geometry.deleteAttribute('normal');
-        geometry.deleteAttribute('uv');
-
-        setupAttributes(geometry);
-
-        // left
-
-        const material1 = new THREE.MeshBasicMaterial({
-            color: 0xe0e0ff,
-            wireframe: true,
-        });
-
-        const mesh1 = new THREE.Mesh(geometry, material1);
-        mesh1.position.set(-40, 0, 0);
-
-        scene.add(mesh1);
-
-        // right
-
-        const material2 = new THREE.ShaderMaterial({
-            uniforms: { thickness: { value: API.thickness } },
-            vertexShader: document.getElementById('vertexShader').textContent,
-            fragmentShader: document.getElementById('fragmentShader').textContent,
-            side: THREE.DoubleSide,
-            alphaToCoverage: true, // only works when WebGLRenderer's "antialias" is set to "true"
-        });
-
-        mesh2 = new THREE.Mesh(geometry, material2);
-        mesh2.position.set(40, 0, 0);
-
-        scene.add(mesh2);
-
-        //
-
-        render();
-    });
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(API, 'thickness', 0, 4).onChange(function () {
-        mesh2.material.uniforms.thickness.value = API.thickness;
-        render();
-    });
-
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function setupAttributes(geometry) {
-    const vectors = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 1)];
-
-    const position = geometry.attributes.position;
-    const centers = new Float32Array(position.count * 3);
-
-    for (let i = 0, l = position.count; i < l; i++) {
-        vectors[i % 3].toArray(centers, i * 3);
-    }
-
-    geometry.setAttribute('center', new THREE.BufferAttribute(centers, 3));
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_math_obb.ts b/examples-testing/examples/webgl_math_obb.ts
deleted file mode 100644
index 48480d10b..000000000
--- a/examples-testing/examples/webgl_math_obb.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-import * as THREE from 'three';
-
-import { OBB } from 'three/addons/math/OBB.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, clock, controls, stats, raycaster, hitbox;
-
-const objects = [],
-    mouse = new THREE.Vector2();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 75);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    clock = new THREE.Clock();
-
-    raycaster = new THREE.Raycaster();
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x222222, 4);
-    hemiLight.position.set(1, 1, 1);
-    scene.add(hemiLight);
-
-    const size = new THREE.Vector3(10, 5, 6);
-    const geometry = new THREE.BoxGeometry(size.x, size.y, size.z);
-
-    // setup OBB on geometry level (doing this manually for now)
-
-    geometry.userData.obb = new OBB();
-    geometry.userData.obb.halfSize.copy(size).multiplyScalar(0.5);
-
-    for (let i = 0; i < 100; i++) {
-        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: 0x00ff00 }));
-        object.matrixAutoUpdate = false;
-
-        object.position.x = Math.random() * 80 - 40;
-        object.position.y = Math.random() * 80 - 40;
-        object.position.z = Math.random() * 80 - 40;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.x = Math.random() + 0.5;
-        object.scale.y = Math.random() + 0.5;
-        object.scale.z = Math.random() + 0.5;
-
-        scene.add(object);
-
-        // bounding volume on object level (this will reflect the current world transform)
-
-        object.userData.obb = new OBB();
-
-        objects.push(object);
-    }
-
-    //
-
-    hitbox = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true }));
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    document.addEventListener('click', onClick);
-}
-
-function onClick(event) {
-    event.preventDefault();
-
-    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-    raycaster.setFromCamera(mouse, camera);
-
-    const intersectionPoint = new THREE.Vector3();
-    const intersections = [];
-
-    for (let i = 0, il = objects.length; i < il; i++) {
-        const object = objects[i];
-        const obb = object.userData.obb;
-
-        const ray = raycaster.ray;
-
-        if (obb.intersectRay(ray, intersectionPoint) !== null) {
-            const distance = ray.origin.distanceTo(intersectionPoint);
-            intersections.push({ distance: distance, object: object });
-        }
-    }
-
-    if (intersections.length > 0) {
-        // determine closest intersection and highlight the respective 3D object
-
-        intersections.sort(sortIntersections);
-
-        intersections[0].object.add(hitbox);
-    } else {
-        const parent = hitbox.parent;
-
-        if (parent) parent.remove(hitbox);
-    }
-}
-
-function sortIntersections(a, b) {
-    return a.distance - b.distance;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    controls.update();
-
-    // transform cubes
-
-    const delta = clock.getDelta();
-
-    for (let i = 0, il = objects.length; i < il; i++) {
-        const object = objects[i];
-
-        object.rotation.x += delta * Math.PI * 0.2;
-        object.rotation.y += delta * Math.PI * 0.1;
-
-        object.updateMatrix();
-        object.updateMatrixWorld();
-
-        // update OBB
-
-        object.userData.obb.copy(object.geometry.userData.obb);
-        object.userData.obb.applyMatrix4(object.matrixWorld);
-
-        // reset
-
-        object.material.color.setHex(0x00ff00);
-    }
-
-    // collision detection
-
-    for (let i = 0, il = objects.length; i < il; i++) {
-        const object = objects[i];
-        const obb = object.userData.obb;
-
-        for (let j = i + 1, jl = objects.length; j < jl; j++) {
-            const objectToTest = objects[j];
-            const obbToTest = objectToTest.userData.obb;
-
-            // now perform intersection test
-
-            if (obb.intersectsOBB(obbToTest) === true) {
-                object.material.color.setHex(0xff0000);
-                objectToTest.material.color.setHex(0xff0000);
-            }
-        }
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_math_orientation_transform.ts b/examples-testing/examples/webgl_math_orientation_transform.ts
deleted file mode 100644
index 99be247d8..000000000
--- a/examples-testing/examples/webgl_math_orientation_transform.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, mesh, target;
-
-const spherical = new THREE.Spherical();
-const rotationMatrix = new THREE.Matrix4();
-const targetQuaternion = new THREE.Quaternion();
-const clock = new THREE.Clock();
-const speed = 2;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.z = 5;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.ConeGeometry(0.1, 0.5, 8);
-    geometry.rotateX(Math.PI * 0.5);
-    const material = new THREE.MeshNormalMaterial();
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const targetGeometry = new THREE.SphereGeometry(0.05);
-    const targetMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
-    target = new THREE.Mesh(targetGeometry, targetMaterial);
-    scene.add(target);
-
-    //
-
-    const sphereGeometry = new THREE.SphereGeometry(2, 32, 32);
-    const sphereMaterial = new THREE.MeshBasicMaterial({
-        color: 0xcccccc,
-        wireframe: true,
-        transparent: true,
-        opacity: 0.3,
-    });
-    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
-    scene.add(sphere);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    generateTarget();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (!mesh.quaternion.equals(targetQuaternion)) {
-        const step = speed * delta;
-        mesh.quaternion.rotateTowards(targetQuaternion, step);
-    }
-
-    renderer.render(scene, camera);
-}
-
-function generateTarget() {
-    // generate a random point on a sphere
-
-    spherical.theta = Math.random() * Math.PI * 2;
-    spherical.phi = Math.acos(2 * Math.random() - 1);
-    spherical.radius = 2;
-
-    target.position.setFromSpherical(spherical);
-
-    // compute target rotation
-
-    rotationMatrix.lookAt(target.position, mesh.position, mesh.up);
-    targetQuaternion.setFromRotationMatrix(rotationMatrix);
-
-    setTimeout(generateTarget, 2000);
-}
diff --git a/examples-testing/examples/webgl_mesh_batch.ts b/examples-testing/examples/webgl_mesh_batch.ts
deleted file mode 100644
index f93e5fb85..000000000
--- a/examples-testing/examples/webgl_mesh_batch.ts
+++ /dev/null
@@ -1,305 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { radixSort } from 'three/addons/utils/SortUtils.js';
-
-let stats, gui, guiStatsEl;
-let camera, controls, scene, renderer;
-let geometries, mesh, material;
-const ids = [];
-const matrix = new THREE.Matrix4();
-
-//
-
-const position = new THREE.Vector3();
-const rotation = new THREE.Euler();
-const quaternion = new THREE.Quaternion();
-const scale = new THREE.Vector3();
-
-//
-
-const MAX_GEOMETRY_COUNT = 20000;
-
-const Method = {
-    BATCHED: 'BATCHED',
-    NAIVE: 'NAIVE',
-};
-
-const api = {
-    method: Method.BATCHED,
-    count: 256,
-    dynamic: 16,
-
-    sortObjects: true,
-    perObjectFrustumCulled: true,
-    opacity: 1,
-    useCustomSort: true,
-};
-
-init();
-initGeometries();
-initMesh();
-
-//
-
-function randomizeMatrix(matrix) {
-    position.x = Math.random() * 40 - 20;
-    position.y = Math.random() * 40 - 20;
-    position.z = Math.random() * 40 - 20;
-
-    rotation.x = Math.random() * 2 * Math.PI;
-    rotation.y = Math.random() * 2 * Math.PI;
-    rotation.z = Math.random() * 2 * Math.PI;
-
-    quaternion.setFromEuler(rotation);
-
-    scale.x = scale.y = scale.z = 0.5 + Math.random() * 0.5;
-
-    return matrix.compose(position, quaternion, scale);
-}
-
-function randomizeRotationSpeed(rotation) {
-    rotation.x = Math.random() * 0.01;
-    rotation.y = Math.random() * 0.01;
-    rotation.z = Math.random() * 0.01;
-    return rotation;
-}
-
-function initGeometries() {
-    geometries = [
-        new THREE.ConeGeometry(1.0, 2.0),
-        new THREE.BoxGeometry(2.0, 2.0, 2.0),
-        new THREE.SphereGeometry(1.0, 16, 8),
-    ];
-}
-
-function createMaterial() {
-    if (!material) {
-        material = new THREE.MeshNormalMaterial();
-    }
-
-    return material;
-}
-
-function cleanup() {
-    if (mesh) {
-        mesh.parent.remove(mesh);
-
-        if (mesh.dispose) {
-            mesh.dispose();
-        }
-    }
-}
-
-function initMesh() {
-    cleanup();
-
-    if (api.method === Method.BATCHED) {
-        initBatchedMesh();
-    } else {
-        initRegularMesh();
-    }
-}
-
-function initRegularMesh() {
-    mesh = new THREE.Group();
-    const material = createMaterial();
-
-    for (let i = 0; i < api.count; i++) {
-        const child = new THREE.Mesh(geometries[i % geometries.length], material);
-        randomizeMatrix(child.matrix);
-        child.matrix.decompose(child.position, child.quaternion, child.scale);
-        child.userData.rotationSpeed = randomizeRotationSpeed(new THREE.Euler());
-        mesh.add(child);
-    }
-
-    scene.add(mesh);
-}
-
-function initBatchedMesh() {
-    const geometryCount = api.count;
-    const vertexCount = geometries.length * 512;
-    const indexCount = geometries.length * 1024;
-
-    const euler = new THREE.Euler();
-    const matrix = new THREE.Matrix4();
-    mesh = new THREE.BatchedMesh(geometryCount, vertexCount, indexCount, createMaterial());
-    mesh.userData.rotationSpeeds = [];
-
-    // disable full-object frustum culling since all of the objects can be dynamic.
-    mesh.frustumCulled = false;
-
-    ids.length = 0;
-
-    const geometryIds = [
-        mesh.addGeometry(geometries[0]),
-        mesh.addGeometry(geometries[1]),
-        mesh.addGeometry(geometries[2]),
-    ];
-
-    for (let i = 0; i < api.count; i++) {
-        const id = mesh.addInstance(geometryIds[i % geometryIds.length]);
-        mesh.setMatrixAt(id, randomizeMatrix(matrix));
-
-        const rotationMatrix = new THREE.Matrix4();
-        rotationMatrix.makeRotationFromEuler(randomizeRotationSpeed(euler));
-        mesh.userData.rotationSpeeds.push(rotationMatrix);
-
-        ids.push(id);
-    }
-
-    scene.add(mesh);
-}
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
-    camera.position.z = 30;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = 1.0;
-
-    // stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // gui
-
-    gui = new GUI();
-    gui.add(api, 'count', 1, MAX_GEOMETRY_COUNT).step(1).onChange(initMesh);
-    gui.add(api, 'dynamic', 0, MAX_GEOMETRY_COUNT).step(1);
-    gui.add(api, 'method', Method).onChange(initMesh);
-    gui.add(api, 'opacity', 0, 1).onChange(v => {
-        if (v < 1) {
-            material.transparent = true;
-            material.depthWrite = false;
-        } else {
-            material.transparent = false;
-            material.depthWrite = true;
-        }
-
-        material.opacity = v;
-        material.needsUpdate = true;
-    });
-    gui.add(api, 'sortObjects');
-    gui.add(api, 'perObjectFrustumCulled');
-    gui.add(api, 'useCustomSort');
-
-    guiStatsEl = document.createElement('li');
-    guiStatsEl.classList.add('gui-stats');
-
-    // listeners
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function sortFunction(list) {
-    // initialize options
-    this._options = this._options || {
-        get: el => el.z,
-        aux: new Array(this.maxInstanceCount),
-    };
-
-    const options = this._options;
-    options.reversed = this.material.transparent;
-
-    let minZ = Infinity;
-    let maxZ = -Infinity;
-    for (let i = 0, l = list.length; i < l; i++) {
-        const z = list[i].z;
-        if (z > maxZ) maxZ = z;
-        if (z < minZ) minZ = z;
-    }
-
-    // convert depth to unsigned 32 bit range
-    const depthDelta = maxZ - minZ;
-    const factor = (2 ** 32 - 1) / depthDelta; // UINT32_MAX / z range
-    for (let i = 0, l = list.length; i < l; i++) {
-        list[i].z -= minZ;
-        list[i].z *= factor;
-    }
-
-    // perform a fast-sort using the hybrid radix sort function
-    radixSort(list, options);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    animateMeshes();
-
-    controls.update();
-    stats.update();
-
-    render();
-}
-
-function animateMeshes() {
-    const loopNum = Math.min(api.count, api.dynamic);
-
-    if (api.method === Method.BATCHED) {
-        for (let i = 0; i < loopNum; i++) {
-            const rotationMatrix = mesh.userData.rotationSpeeds[i];
-            const id = ids[i];
-
-            mesh.getMatrixAt(id, matrix);
-            matrix.multiply(rotationMatrix);
-            mesh.setMatrixAt(id, matrix);
-        }
-    } else {
-        for (let i = 0; i < loopNum; i++) {
-            const child = mesh.children[i];
-            const rotationSpeed = child.userData.rotationSpeed;
-
-            child.rotation.set(
-                child.rotation.x + rotationSpeed.x,
-                child.rotation.y + rotationSpeed.y,
-                child.rotation.z + rotationSpeed.z,
-            );
-        }
-    }
-}
-
-function render() {
-    if (mesh.isBatchedMesh) {
-        mesh.sortObjects = api.sortObjects;
-        mesh.perObjectFrustumCulled = api.perObjectFrustumCulled;
-        mesh.setCustomSort(api.useCustomSort ? sortFunction : null);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_mirror.ts b/examples-testing/examples/webgl_mirror.ts
deleted file mode 100644
index 8b27363a8..000000000
--- a/examples-testing/examples/webgl_mirror.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Reflector } from 'three/addons/objects/Reflector.js';
-
-let camera, scene, renderer;
-
-let cameraControls;
-
-let sphereGroup, smallSphere;
-
-let groundMirror, verticalMirror;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.set(0, 75, 160);
-
-    cameraControls = new OrbitControls(camera, renderer.domElement);
-    cameraControls.target.set(0, 40, 0);
-    cameraControls.maxDistance = 400;
-    cameraControls.minDistance = 10;
-    cameraControls.update();
-
-    //
-
-    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
-
-    // reflectors/mirrors
-
-    let geometry, material;
-
-    geometry = new THREE.CircleGeometry(40, 64);
-    groundMirror = new Reflector(geometry, {
-        clipBias: 0.003,
-        textureWidth: window.innerWidth * window.devicePixelRatio,
-        textureHeight: window.innerHeight * window.devicePixelRatio,
-        color: 0xb5b5b5,
-    });
-    groundMirror.position.y = 0.5;
-    groundMirror.rotateX(-Math.PI / 2);
-    scene.add(groundMirror);
-
-    geometry = new THREE.PlaneGeometry(100, 100);
-    verticalMirror = new Reflector(geometry, {
-        clipBias: 0.003,
-        textureWidth: window.innerWidth * window.devicePixelRatio,
-        textureHeight: window.innerHeight * window.devicePixelRatio,
-        color: 0xc1cbcb,
-    });
-    verticalMirror.position.y = 50;
-    verticalMirror.position.z = -50;
-    scene.add(verticalMirror);
-
-    sphereGroup = new THREE.Object3D();
-    scene.add(sphereGroup);
-
-    geometry = new THREE.CylinderGeometry(0.1, 15 * Math.cos((Math.PI / 180) * 30), 0.1, 24, 1);
-    material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x8d8d8d });
-    const sphereCap = new THREE.Mesh(geometry, material);
-    sphereCap.position.y = -15 * Math.sin((Math.PI / 180) * 30) - 0.05;
-    sphereCap.rotateX(-Math.PI);
-
-    geometry = new THREE.SphereGeometry(15, 24, 24, Math.PI / 2, Math.PI * 2, 0, (Math.PI / 180) * 120);
-    const halfSphere = new THREE.Mesh(geometry, material);
-    halfSphere.add(sphereCap);
-    halfSphere.rotateX((-Math.PI / 180) * 135);
-    halfSphere.rotateZ((-Math.PI / 180) * 20);
-    halfSphere.position.y = 7.5 + 15 * Math.sin((Math.PI / 180) * 30);
-
-    sphereGroup.add(halfSphere);
-
-    geometry = new THREE.IcosahedronGeometry(5, 0);
-    material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x7b7b7b, flatShading: true });
-    smallSphere = new THREE.Mesh(geometry, material);
-    scene.add(smallSphere);
-
-    // walls
-    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeTop.position.y = 100;
-    planeTop.rotateX(Math.PI / 2);
-    scene.add(planeTop);
-
-    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeBottom.rotateX(-Math.PI / 2);
-    scene.add(planeBottom);
-
-    const planeFront = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
-    planeFront.position.z = 50;
-    planeFront.position.y = 50;
-    planeFront.rotateY(Math.PI);
-    scene.add(planeFront);
-
-    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
-    planeRight.position.x = 50;
-    planeRight.position.y = 50;
-    planeRight.rotateY(-Math.PI / 2);
-    scene.add(planeRight);
-
-    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
-    planeLeft.position.x = -50;
-    planeLeft.position.y = 50;
-    planeLeft.rotateY(Math.PI / 2);
-    scene.add(planeLeft);
-
-    // lights
-    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
-    mainLight.position.y = 60;
-    scene.add(mainLight);
-
-    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
-    greenLight.position.set(550, 50, 0);
-    scene.add(greenLight);
-
-    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
-    redLight.position.set(-550, 50, 0);
-    scene.add(redLight);
-
-    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
-    blueLight.position.set(0, 50, 550);
-    scene.add(blueLight);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    groundMirror
-        .getRenderTarget()
-        .setSize(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio);
-    verticalMirror
-        .getRenderTarget()
-        .setSize(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio);
-}
-
-function animate() {
-    const timer = Date.now() * 0.01;
-
-    sphereGroup.rotation.y -= 0.002;
-
-    smallSphere.position.set(
-        Math.cos(timer * 0.1) * 30,
-        Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
-        Math.sin(timer * 0.1) * 30,
-    );
-    smallSphere.rotation.y = Math.PI / 2 - timer * 0.1;
-    smallSphere.rotation.z = timer * 0.8;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_modifier_edgesplit.ts b/examples-testing/examples/webgl_modifier_edgesplit.ts
deleted file mode 100644
index 4725eff62..000000000
--- a/examples-testing/examples/webgl_modifier_edgesplit.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { EdgeSplitModifier } from 'three/addons/modifiers/EdgeSplitModifier.js';
-import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let renderer, scene, camera;
-let modifier, mesh, baseGeometry;
-let map;
-
-const params = {
-    smoothShading: true,
-    edgeSplit: true,
-    cutOffAngle: 20,
-    showMap: false,
-    tryKeepNormals: true,
-};
-
-init();
-
-function init() {
-    const info = document.createElement('div');
-    info.style.position = 'absolute';
-    info.style.top = '10px';
-    info.style.width = '100%';
-    info.style.textAlign = 'center';
-    info.innerHTML = '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Edge Split modifier';
-    document.body.appendChild(info);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.enableDamping = true;
-    controls.dampingFactor = 0.25;
-    controls.rotateSpeed = 0.35;
-    controls.minZoom = 1;
-    camera.position.set(0, 0, 4);
-
-    scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 3));
-
-    new OBJLoader().load('./models/obj/cerberus/Cerberus.obj', function (group) {
-        const cerberus = group.children[0];
-        const modelGeometry = cerberus.geometry;
-
-        modifier = new EdgeSplitModifier();
-        baseGeometry = BufferGeometryUtils.mergeVertices(modelGeometry);
-
-        mesh = new THREE.Mesh(getGeometry(), new THREE.MeshStandardMaterial());
-        mesh.material.flatShading = !params.smoothShading;
-        mesh.rotateY(-Math.PI / 2);
-        mesh.scale.set(3.5, 3.5, 3.5);
-        mesh.translateZ(1.5);
-        scene.add(mesh);
-
-        if (map !== undefined && params.showMap) {
-            mesh.material.map = map;
-            mesh.material.needsUpdate = true;
-        }
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-
-    new THREE.TextureLoader().load('./models/obj/cerberus/Cerberus_A.jpg', function (texture) {
-        map = texture;
-        map.colorSpace = THREE.SRGBColorSpace;
-
-        if (mesh !== undefined && params.showMap) {
-            mesh.material.map = map;
-            mesh.material.needsUpdate = true;
-        }
-    });
-
-    const gui = new GUI({ title: 'Edge split modifier parameters' });
-
-    gui.add(params, 'showMap').onFinishChange(updateMesh);
-    gui.add(params, 'smoothShading').onFinishChange(updateMesh);
-    gui.add(params, 'edgeSplit').onFinishChange(updateMesh);
-    gui.add(params, 'cutOffAngle').min(0).max(180).onFinishChange(updateMesh);
-    gui.add(params, 'tryKeepNormals').onFinishChange(updateMesh);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function getGeometry() {
-    let geometry;
-
-    if (params.edgeSplit) {
-        geometry = modifier.modify(baseGeometry, (params.cutOffAngle * Math.PI) / 180, params.tryKeepNormals);
-    } else {
-        geometry = baseGeometry;
-    }
-
-    return geometry;
-}
-
-function updateMesh() {
-    if (mesh !== undefined) {
-        mesh.geometry = getGeometry();
-
-        let needsUpdate = mesh.material.flatShading === params.smoothShading;
-        mesh.material.flatShading = params.smoothShading === false;
-
-        if (map !== undefined) {
-            needsUpdate = needsUpdate || mesh.material.map !== (params.showMap ? map : null);
-            mesh.material.map = params.showMap ? map : null;
-        }
-
-        mesh.material.needsUpdate = needsUpdate;
-
-        render();
-    }
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_modifier_simplifier.ts b/examples-testing/examples/webgl_modifier_simplifier.ts
deleted file mode 100644
index e6ea453b3..000000000
--- a/examples-testing/examples/webgl_modifier_simplifier.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';
-
-let renderer, scene, camera;
-
-init();
-
-function init() {
-    const info = document.createElement('div');
-    info.style.position = 'absolute';
-    info.style.top = '10px';
-    info.style.width = '100%';
-    info.style.textAlign = 'center';
-    info.innerHTML =
-        '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Vertex Reduction using SimplifyModifier';
-    document.body.appendChild(info);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 15;
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.enablePan = false;
-    controls.enableZoom = false;
-
-    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
-
-    const light = new THREE.PointLight(0xffffff, 400);
-    camera.add(light);
-    scene.add(camera);
-
-    new GLTFLoader().load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-        mesh.position.x = -3;
-        mesh.rotation.y = Math.PI / 2;
-        scene.add(mesh);
-
-        const modifier = new SimplifyModifier();
-
-        const simplified = mesh.clone();
-        simplified.material = simplified.material.clone();
-        simplified.material.flatShading = true;
-        const count = Math.floor(simplified.geometry.attributes.position.count * 0.875); // number of vertices to remove
-        simplified.geometry = modifier.modify(simplified.geometry, count);
-
-        simplified.position.x = 3;
-        simplified.rotation.y = -Math.PI / 2;
-        scene.add(simplified);
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_modifier_tessellation.ts b/examples-testing/examples/webgl_modifier_tessellation.ts
deleted file mode 100644
index 4600fc6cb..000000000
--- a/examples-testing/examples/webgl_modifier_tessellation.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
-import { TessellateModifier } from 'three/addons/modifiers/TessellateModifier.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-let renderer, scene, camera, stats;
-
-let controls;
-
-let mesh, uniforms;
-
-const WIDTH = window.innerWidth;
-const HEIGHT = window.innerHeight;
-
-const loader = new FontLoader();
-loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
-    init(font);
-});
-
-function init(font) {
-    camera = new THREE.PerspectiveCamera(40, WIDTH / HEIGHT, 1, 10000);
-    camera.position.set(-100, 100, 200);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x050505);
-
-    //
-
-    let geometry = new TextGeometry('THREE.JS', {
-        font: font,
-
-        size: 40,
-        depth: 5,
-        curveSegments: 3,
-
-        bevelThickness: 2,
-        bevelSize: 1,
-        bevelEnabled: true,
-    });
-
-    geometry.center();
-
-    const tessellateModifier = new TessellateModifier(8, 6);
-
-    geometry = tessellateModifier.modify(geometry);
-
-    //
-
-    const numFaces = geometry.attributes.position.count / 3;
-
-    const colors = new Float32Array(numFaces * 3 * 3);
-    const displacement = new Float32Array(numFaces * 3 * 3);
-
-    const color = new THREE.Color();
-
-    for (let f = 0; f < numFaces; f++) {
-        const index = 9 * f;
-
-        const h = 0.2 * Math.random();
-        const s = 0.5 + 0.5 * Math.random();
-        const l = 0.5 + 0.5 * Math.random();
-
-        color.setHSL(h, s, l);
-
-        const d = 10 * (0.5 - Math.random());
-
-        for (let i = 0; i < 3; i++) {
-            colors[index + 3 * i] = color.r;
-            colors[index + 3 * i + 1] = color.g;
-            colors[index + 3 * i + 2] = color.b;
-
-            displacement[index + 3 * i] = d;
-            displacement[index + 3 * i + 1] = d;
-            displacement[index + 3 * i + 2] = d;
-        }
-    }
-
-    geometry.setAttribute('customColor', new THREE.BufferAttribute(colors, 3));
-    geometry.setAttribute('displacement', new THREE.BufferAttribute(displacement, 3));
-
-    //
-
-    uniforms = {
-        amplitude: { value: 0.0 },
-    };
-
-    const shaderMaterial = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    //
-
-    mesh = new THREE.Mesh(geometry, shaderMaterial);
-
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(WIDTH, HEIGHT);
-    renderer.setAnimationLoop(animate);
-
-    const container = document.getElementById('container');
-    container.appendChild(renderer.domElement);
-
-    controls = new TrackballControls(camera, renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.001;
-
-    uniforms.amplitude.value = 1.0 + Math.sin(time * 0.5);
-
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_morphtargets.ts b/examples-testing/examples/webgl_morphtargets.ts
deleted file mode 100644
index 40d605f8d..000000000
--- a/examples-testing/examples/webgl_morphtargets.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, camera, scene, renderer, mesh;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x8fbcd4);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
-    camera.position.z = 10;
-    scene.add(camera);
-
-    scene.add(new THREE.AmbientLight(0x8fbcd4, 1.5));
-
-    const pointLight = new THREE.PointLight(0xffffff, 200);
-    camera.add(pointLight);
-
-    const geometry = createGeometry();
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xff0000,
-        flatShading: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    initGUI();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(function () {
-        renderer.render(scene, camera);
-    });
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createGeometry() {
-    const geometry = new THREE.BoxGeometry(2, 2, 2, 32, 32, 32);
-
-    // create an empty array to  hold targets for the attribute we want to morph
-    // morphing positions and normals is supported
-    geometry.morphAttributes.position = [];
-
-    // the original positions of the cube's vertices
-    const positionAttribute = geometry.attributes.position;
-
-    // for the first morph target we'll move the cube's vertices onto the surface of a sphere
-    const spherePositions = [];
-
-    // for the second morph target, we'll twist the cubes vertices
-    const twistPositions = [];
-    const direction = new THREE.Vector3(1, 0, 0);
-    const vertex = new THREE.Vector3();
-
-    for (let i = 0; i < positionAttribute.count; i++) {
-        const x = positionAttribute.getX(i);
-        const y = positionAttribute.getY(i);
-        const z = positionAttribute.getZ(i);
-
-        spherePositions.push(
-            x * Math.sqrt(1 - (y * y) / 2 - (z * z) / 2 + (y * y * z * z) / 3),
-            y * Math.sqrt(1 - (z * z) / 2 - (x * x) / 2 + (z * z * x * x) / 3),
-            z * Math.sqrt(1 - (x * x) / 2 - (y * y) / 2 + (x * x * y * y) / 3),
-        );
-
-        // stretch along the x-axis so we can see the twist better
-        vertex.set(x * 2, y, z);
-
-        vertex.applyAxisAngle(direction, (Math.PI * x) / 2).toArray(twistPositions, twistPositions.length);
-    }
-
-    // add the spherical positions as the first morph target
-    geometry.morphAttributes.position[0] = new THREE.Float32BufferAttribute(spherePositions, 3);
-
-    // add the twisted positions as the second morph target
-    geometry.morphAttributes.position[1] = new THREE.Float32BufferAttribute(twistPositions, 3);
-
-    return geometry;
-}
-
-function initGUI() {
-    // Set up dat.GUI to control targets
-    const params = {
-        Spherify: 0,
-        Twist: 0,
-    };
-    const gui = new GUI({ title: 'Morph Targets' });
-
-    gui.add(params, 'Spherify', 0, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            mesh.morphTargetInfluences[0] = value;
-        });
-    gui.add(params, 'Twist', 0, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            mesh.morphTargetInfluences[1] = value;
-        });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
diff --git a/examples-testing/examples/webgl_morphtargets_face.ts b/examples-testing/examples/webgl_morphtargets_face.ts
deleted file mode 100644
index 76179d902..000000000
--- a/examples-testing/examples/webgl_morphtargets_face.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
-
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats, mixer, clock, controls;
-
-init();
-
-function init() {
-    clock = new THREE.Clock();
-
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
-    camera.position.set(-1.8, 0.8, 3);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-
-    container.appendChild(renderer.domElement);
-
-    const ktx2Loader = new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupport(renderer);
-
-    new GLTFLoader()
-        .setKTX2Loader(ktx2Loader)
-        .setMeshoptDecoder(MeshoptDecoder)
-        .load('models/gltf/facecap.glb', gltf => {
-            const mesh = gltf.scene.children[0];
-
-            scene.add(mesh);
-
-            mixer = new THREE.AnimationMixer(mesh);
-
-            mixer.clipAction(gltf.animations[0]).play();
-
-            // GUI
-
-            const head = mesh.getObjectByName('mesh_2');
-            const influences = head.morphTargetInfluences;
-
-            const gui = new GUI();
-            gui.close();
-
-            for (const [key, value] of Object.entries(head.morphTargetDictionary)) {
-                gui.add(influences, value, 0, 1, 0.01).name(key.replace('blendShape1.', '')).listen();
-            }
-        });
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene.background = new THREE.Color(0x666666);
-    scene.environment = pmremGenerator.fromScene(environment).texture;
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 2.5;
-    controls.maxDistance = 5;
-    controls.minAzimuthAngle = -Math.PI / 2;
-    controls.maxAzimuthAngle = Math.PI / 2;
-    controls.maxPolarAngle = Math.PI / 1.8;
-    controls.target.set(0, 0.15, -0.2);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) {
-        mixer.update(delta);
-    }
-
-    renderer.render(scene, camera);
-
-    controls.update();
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_morphtargets_horse.ts b/examples-testing/examples/webgl_morphtargets_horse.ts
deleted file mode 100644
index 2c29e9c0e..000000000
--- a/examples-testing/examples/webgl_morphtargets_horse.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let container, stats;
-let camera, scene, renderer;
-let mesh, mixer;
-
-const radius = 600;
-let theta = 0;
-let prevTime = Date.now();
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.y = 300;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xf0f0f0);
-
-    //
-
-    const light1 = new THREE.DirectionalLight(0xefefff, 5);
-    light1.position.set(1, 1, 1).normalize();
-    scene.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffefef, 5);
-    light2.position.set(-1, -1, -1).normalize();
-    scene.add(light2);
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/Horse.glb', function (gltf) {
-        mesh = gltf.scene.children[0];
-        mesh.scale.set(1.5, 1.5, 1.5);
-        scene.add(mesh);
-
-        mixer = new THREE.AnimationMixer(mesh);
-
-        mixer.clipAction(gltf.animations[0]).setDuration(1).play();
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    theta += 0.1;
-
-    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
-    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
-
-    camera.lookAt(0, 150, 0);
-
-    if (mixer) {
-        const time = Date.now();
-
-        mixer.update((time - prevTime) * 0.001);
-
-        prevTime = time;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_morphtargets_sphere.ts b/examples-testing/examples/webgl_morphtargets_sphere.ts
deleted file mode 100644
index 2b8899111..000000000
--- a/examples-testing/examples/webgl_morphtargets_sphere.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { Timer } from 'three/addons/misc/Timer.js';
-
-let camera, scene, renderer, timer;
-
-let mesh;
-
-let sign = 1;
-const speed = 0.5;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.2, 100);
-    camera.position.set(0, 5, 5);
-
-    scene = new THREE.Scene();
-
-    timer = new Timer();
-
-    const light1 = new THREE.PointLight(0xff2200, 50000);
-    light1.position.set(100, 100, 100);
-    scene.add(light1);
-
-    const light2 = new THREE.PointLight(0x22ff00, 10000);
-    light2.position.set(-100, -100, -100);
-    scene.add(light2);
-
-    scene.add(new THREE.AmbientLight(0x111111));
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/AnimatedMorphSphere/glTF/AnimatedMorphSphere.gltf', function (gltf) {
-        mesh = gltf.scene.getObjectByName('AnimatedMorphSphere');
-        mesh.rotation.z = Math.PI / 2;
-        scene.add(mesh);
-
-        //
-
-        const pointsMaterial = new THREE.PointsMaterial({
-            size: 10,
-            sizeAttenuation: false,
-            map: new THREE.TextureLoader().load('textures/sprites/disc.png'),
-            alphaTest: 0.5,
-        });
-
-        const points = new THREE.Points(mesh.geometry, pointsMaterial);
-        points.morphTargetInfluences = mesh.morphTargetInfluences;
-        points.morphTargetDictionary = mesh.morphTargetDictionary;
-        mesh.add(points);
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 20;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    timer.update();
-    render();
-}
-
-function render() {
-    const delta = timer.getDelta();
-
-    if (mesh !== undefined) {
-        const step = delta * speed;
-
-        mesh.rotation.y += step;
-
-        mesh.morphTargetInfluences[1] = mesh.morphTargetInfluences[1] + step * sign;
-
-        if (mesh.morphTargetInfluences[1] <= 0 || mesh.morphTargetInfluences[1] >= 1) {
-            sign *= -1;
-        }
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_multiple_elements.ts b/examples-testing/examples/webgl_multiple_elements.ts
deleted file mode 100644
index 64f8a9c5f..000000000
--- a/examples-testing/examples/webgl_multiple_elements.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let canvas, renderer;
-
-const scenes = [];
-
-init();
-
-function init() {
-    canvas = document.getElementById('c');
-
-    const geometries = [
-        new THREE.BoxGeometry(1, 1, 1),
-        new THREE.SphereGeometry(0.5, 12, 8),
-        new THREE.DodecahedronGeometry(0.5),
-        new THREE.CylinderGeometry(0.5, 0.5, 1, 12),
-    ];
-
-    const content = document.getElementById('content');
-
-    for (let i = 0; i < 40; i++) {
-        const scene = new THREE.Scene();
-
-        // make a list item
-        const element = document.createElement('div');
-        element.className = 'list-item';
-
-        const sceneElement = document.createElement('div');
-        element.appendChild(sceneElement);
-
-        const descriptionElement = document.createElement('div');
-        descriptionElement.innerText = 'Scene ' + (i + 1);
-        element.appendChild(descriptionElement);
-
-        // the element that represents the area we want to render the scene
-        scene.userData.element = sceneElement;
-        content.appendChild(element);
-
-        const camera = new THREE.PerspectiveCamera(50, 1, 1, 10);
-        camera.position.z = 2;
-        scene.userData.camera = camera;
-
-        const controls = new OrbitControls(scene.userData.camera, scene.userData.element);
-        controls.minDistance = 2;
-        controls.maxDistance = 5;
-        controls.enablePan = false;
-        controls.enableZoom = false;
-        scene.userData.controls = controls;
-
-        // add one random mesh to each scene
-        const geometry = geometries[(geometries.length * Math.random()) | 0];
-
-        const material = new THREE.MeshStandardMaterial({
-            color: new THREE.Color().setHSL(Math.random(), 1, 0.75, THREE.SRGBColorSpace),
-            roughness: 0.5,
-            metalness: 0,
-            flatShading: true,
-        });
-
-        scene.add(new THREE.Mesh(geometry, material));
-
-        scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, 3));
-
-        const light = new THREE.DirectionalLight(0xffffff, 1.5);
-        light.position.set(1, 1, 1);
-        scene.add(light);
-
-        scenes.push(scene);
-    }
-
-    renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
-    renderer.setClearColor(0xffffff, 1);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-}
-
-function updateSize() {
-    const width = canvas.clientWidth;
-    const height = canvas.clientHeight;
-
-    if (canvas.width !== width || canvas.height !== height) {
-        renderer.setSize(width, height, false);
-    }
-}
-
-function animate() {
-    updateSize();
-
-    canvas.style.transform = `translateY(${window.scrollY}px)`;
-
-    renderer.setClearColor(0xffffff);
-    renderer.setScissorTest(false);
-    renderer.clear();
-
-    renderer.setClearColor(0xe0e0e0);
-    renderer.setScissorTest(true);
-
-    scenes.forEach(function (scene) {
-        // so something moves
-        scene.children[0].rotation.y = Date.now() * 0.001;
-
-        // get the element that is a place holder for where we want to
-        // draw the scene
-        const element = scene.userData.element;
-
-        // get its position relative to the page's viewport
-        const rect = element.getBoundingClientRect();
-
-        // check if it's offscreen. If so skip it
-        if (
-            rect.bottom < 0 ||
-            rect.top > renderer.domElement.clientHeight ||
-            rect.right < 0 ||
-            rect.left > renderer.domElement.clientWidth
-        ) {
-            return; // it's off screen
-        }
-
-        // set the viewport
-        const width = rect.right - rect.left;
-        const height = rect.bottom - rect.top;
-        const left = rect.left;
-        const bottom = renderer.domElement.clientHeight - rect.bottom;
-
-        renderer.setViewport(left, bottom, width, height);
-        renderer.setScissor(left, bottom, width, height);
-
-        const camera = scene.userData.camera;
-
-        //camera.aspect = width / height; // not changing in this example
-        //camera.updateProjectionMatrix();
-
-        //scene.userData.controls.update();
-
-        renderer.render(scene, camera);
-    });
-}
diff --git a/examples-testing/examples/webgl_multiple_rendertargets.ts b/examples-testing/examples/webgl_multiple_rendertargets.ts
deleted file mode 100644
index 86708082b..000000000
--- a/examples-testing/examples/webgl_multiple_rendertargets.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, controls;
-let renderTarget;
-let postScene, postCamera;
-
-const parameters = {
-    samples: 4,
-    wireframe: false,
-};
-
-const gui = new GUI();
-gui.add(parameters, 'samples', 0, 4).step(1);
-gui.add(parameters, 'wireframe');
-gui.onChange(render);
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    // Create a multi render target with Float buffers
-
-    renderTarget = new THREE.WebGLRenderTarget(
-        window.innerWidth * window.devicePixelRatio,
-        window.innerHeight * window.devicePixelRatio,
-        {
-            count: 2,
-            minFilter: THREE.NearestFilter,
-            magFilter: THREE.NearestFilter,
-        },
-    );
-
-    // Name our G-Buffer attachments for debugging
-
-    renderTarget.textures[0].name = 'diffuse';
-    renderTarget.textures[1].name = 'normal';
-
-    // Scene setup
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x222222);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 50);
-    camera.position.z = 4;
-
-    const loader = new THREE.TextureLoader();
-
-    const diffuse = loader.load('textures/hardwood2_diffuse.jpg', render);
-    diffuse.wrapS = THREE.RepeatWrapping;
-    diffuse.wrapT = THREE.RepeatWrapping;
-    diffuse.colorSpace = THREE.SRGBColorSpace;
-
-    scene.add(
-        new THREE.Mesh(
-            new THREE.TorusKnotGeometry(1, 0.3, 128, 32),
-            new THREE.RawShaderMaterial({
-                name: 'G-Buffer Shader',
-                vertexShader: document.querySelector('#gbuffer-vert').textContent.trim(),
-                fragmentShader: document.querySelector('#gbuffer-frag').textContent.trim(),
-                uniforms: {
-                    tDiffuse: { value: diffuse },
-                    repeat: { value: new THREE.Vector2(5, 0.5) },
-                },
-                glslVersion: THREE.GLSL3,
-            }),
-        ),
-    );
-
-    // PostProcessing setup
-
-    postScene = new THREE.Scene();
-    postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
-
-    postScene.add(
-        new THREE.Mesh(
-            new THREE.PlaneGeometry(2, 2),
-            new THREE.RawShaderMaterial({
-                name: 'Post-FX Shader',
-                vertexShader: document.querySelector('#render-vert').textContent.trim(),
-                fragmentShader: document.querySelector('#render-frag').textContent.trim(),
-                uniforms: {
-                    tDiffuse: { value: renderTarget.textures[0] },
-                    tNormal: { value: renderTarget.textures[1] },
-                },
-                glslVersion: THREE.GLSL3,
-            }),
-        ),
-    );
-
-    // Controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    const dpr = renderer.getPixelRatio();
-    renderTarget.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
-
-    render();
-}
-
-function render() {
-    renderTarget.samples = parameters.samples;
-
-    scene.traverse(function (child) {
-        if (child.material !== undefined) {
-            child.material.wireframe = parameters.wireframe;
-        }
-    });
-
-    // render scene into target
-    renderer.setRenderTarget(renderTarget);
-    renderer.render(scene, camera);
-
-    // render post FX
-    renderer.setRenderTarget(null);
-    renderer.render(postScene, postCamera);
-}
diff --git a/examples-testing/examples/webgl_multiple_scenes_comparison.ts b/examples-testing/examples/webgl_multiple_scenes_comparison.ts
deleted file mode 100644
index 41a5130d4..000000000
--- a/examples-testing/examples/webgl_multiple_scenes_comparison.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, camera, renderer, controls;
-let sceneL, sceneR;
-
-let sliderPos = window.innerWidth / 2;
-
-init();
-
-function init() {
-    container = document.querySelector('.container');
-
-    sceneL = new THREE.Scene();
-    sceneL.background = new THREE.Color(0xbcd48f);
-
-    sceneR = new THREE.Scene();
-    sceneR.background = new THREE.Color(0x8fbcd4);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 6;
-
-    controls = new OrbitControls(camera, container);
-
-    const light = new THREE.HemisphereLight(0xffffff, 0x444444, 3);
-    light.position.set(-2, 2, 2);
-    sceneL.add(light.clone());
-    sceneR.add(light.clone());
-
-    initMeshes();
-    initSlider();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setScissorTest(true);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function initMeshes() {
-    const geometry = new THREE.IcosahedronGeometry(1, 3);
-
-    const meshL = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
-    sceneL.add(meshL);
-
-    const meshR = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ wireframe: true }));
-    sceneR.add(meshR);
-}
-
-function initSlider() {
-    const slider = document.querySelector('.slider');
-
-    function onPointerDown() {
-        if (event.isPrimary === false) return;
-
-        controls.enabled = false;
-
-        window.addEventListener('pointermove', onPointerMove);
-        window.addEventListener('pointerup', onPointerUp);
-    }
-
-    function onPointerUp() {
-        controls.enabled = true;
-
-        window.removeEventListener('pointermove', onPointerMove);
-        window.removeEventListener('pointerup', onPointerUp);
-    }
-
-    function onPointerMove(e) {
-        if (event.isPrimary === false) return;
-
-        sliderPos = Math.max(0, Math.min(window.innerWidth, e.pageX));
-
-        slider.style.left = sliderPos - slider.offsetWidth / 2 + 'px';
-    }
-
-    slider.style.touchAction = 'none'; // disable touch scroll
-    slider.addEventListener('pointerdown', onPointerDown);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.setScissor(0, 0, sliderPos, window.innerHeight);
-    renderer.render(sceneL, camera);
-
-    renderer.setScissor(sliderPos, 0, window.innerWidth, window.innerHeight);
-    renderer.render(sceneR, camera);
-}
diff --git a/examples-testing/examples/webgl_multiple_views.ts b/examples-testing/examples/webgl_multiple_views.ts
deleted file mode 100644
index 29126b013..000000000
--- a/examples-testing/examples/webgl_multiple_views.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let stats;
-
-let scene, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowWidth, windowHeight;
-
-const views = [
-    {
-        left: 0,
-        bottom: 0,
-        width: 0.5,
-        height: 1.0,
-        background: new THREE.Color().setRGB(0.5, 0.5, 0.7, THREE.SRGBColorSpace),
-        eye: [0, 300, 1800],
-        up: [0, 1, 0],
-        fov: 30,
-        updateCamera: function (camera, scene, mouseX) {
-            camera.position.x += mouseX * 0.05;
-            camera.position.x = Math.max(Math.min(camera.position.x, 2000), -2000);
-            camera.lookAt(scene.position);
-        },
-    },
-    {
-        left: 0.5,
-        bottom: 0,
-        width: 0.5,
-        height: 0.5,
-        background: new THREE.Color().setRGB(0.7, 0.5, 0.5, THREE.SRGBColorSpace),
-        eye: [0, 1800, 0],
-        up: [0, 0, 1],
-        fov: 45,
-        updateCamera: function (camera, scene, mouseX) {
-            camera.position.x -= mouseX * 0.05;
-            camera.position.x = Math.max(Math.min(camera.position.x, 2000), -2000);
-            camera.lookAt(camera.position.clone().setY(0));
-        },
-    },
-    {
-        left: 0.5,
-        bottom: 0.5,
-        width: 0.5,
-        height: 0.5,
-        background: new THREE.Color().setRGB(0.5, 0.7, 0.7, THREE.SRGBColorSpace),
-        eye: [1400, 800, 1400],
-        up: [0, 1, 0],
-        fov: 60,
-        updateCamera: function (camera, scene, mouseX) {
-            camera.position.y -= mouseX * 0.05;
-            camera.position.y = Math.max(Math.min(camera.position.y, 1600), -1600);
-            camera.lookAt(scene.position);
-        },
-    },
-];
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    for (let ii = 0; ii < views.length; ++ii) {
-        const view = views[ii];
-        const camera = new THREE.PerspectiveCamera(view.fov, window.innerWidth / window.innerHeight, 1, 10000);
-        camera.position.fromArray(view.eye);
-        camera.up.fromArray(view.up);
-        view.camera = camera;
-    }
-
-    scene = new THREE.Scene();
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 0, 1);
-    scene.add(light);
-
-    // shadow
-
-    const canvas = document.createElement('canvas');
-    canvas.width = 128;
-    canvas.height = 128;
-
-    const context = canvas.getContext('2d');
-    const gradient = context.createRadialGradient(
-        canvas.width / 2,
-        canvas.height / 2,
-        0,
-        canvas.width / 2,
-        canvas.height / 2,
-        canvas.width / 2,
-    );
-    gradient.addColorStop(0.1, 'rgba(0,0,0,0.15)');
-    gradient.addColorStop(1, 'rgba(0,0,0,0)');
-
-    context.fillStyle = gradient;
-    context.fillRect(0, 0, canvas.width, canvas.height);
-
-    const shadowTexture = new THREE.CanvasTexture(canvas);
-
-    const shadowMaterial = new THREE.MeshBasicMaterial({ map: shadowTexture, transparent: true });
-    const shadowGeo = new THREE.PlaneGeometry(300, 300, 1, 1);
-
-    let shadowMesh;
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.y = -250;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.x = -400;
-    shadowMesh.position.y = -250;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    shadowMesh = new THREE.Mesh(shadowGeo, shadowMaterial);
-    shadowMesh.position.x = 400;
-    shadowMesh.position.y = -250;
-    shadowMesh.rotation.x = -Math.PI / 2;
-    scene.add(shadowMesh);
-
-    const radius = 200;
-
-    const geometry1 = new THREE.IcosahedronGeometry(radius, 1);
-
-    const count = geometry1.attributes.position.count;
-    geometry1.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
-
-    const geometry2 = geometry1.clone();
-    const geometry3 = geometry1.clone();
-
-    const color = new THREE.Color();
-    const positions1 = geometry1.attributes.position;
-    const positions2 = geometry2.attributes.position;
-    const positions3 = geometry3.attributes.position;
-    const colors1 = geometry1.attributes.color;
-    const colors2 = geometry2.attributes.color;
-    const colors3 = geometry3.attributes.color;
-
-    for (let i = 0; i < count; i++) {
-        color.setHSL((positions1.getY(i) / radius + 1) / 2, 1.0, 0.5, THREE.SRGBColorSpace);
-        colors1.setXYZ(i, color.r, color.g, color.b);
-
-        color.setHSL(0, (positions2.getY(i) / radius + 1) / 2, 0.5, THREE.SRGBColorSpace);
-        colors2.setXYZ(i, color.r, color.g, color.b);
-
-        color.setRGB(1, 0.8 - (positions3.getY(i) / radius + 1) / 2, 0, THREE.SRGBColorSpace);
-        colors3.setXYZ(i, color.r, color.g, color.b);
-    }
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xffffff,
-        flatShading: true,
-        vertexColors: true,
-        shininess: 0,
-    });
-
-    const wireframeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true, transparent: true });
-
-    let mesh = new THREE.Mesh(geometry1, material);
-    let wireframe = new THREE.Mesh(geometry1, wireframeMaterial);
-    mesh.add(wireframe);
-    mesh.position.x = -400;
-    mesh.rotation.x = -1.87;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry2, material);
-    wireframe = new THREE.Mesh(geometry2, wireframeMaterial);
-    mesh.add(wireframe);
-    mesh.position.x = 400;
-    scene.add(mesh);
-
-    mesh = new THREE.Mesh(geometry3, material);
-    wireframe = new THREE.Mesh(geometry3, wireframeMaterial);
-    mesh.add(wireframe);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowWidth / 2;
-    mouseY = event.clientY - windowHeight / 2;
-}
-
-function updateSize() {
-    if (windowWidth != window.innerWidth || windowHeight != window.innerHeight) {
-        windowWidth = window.innerWidth;
-        windowHeight = window.innerHeight;
-
-        renderer.setSize(windowWidth, windowHeight);
-    }
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    updateSize();
-
-    for (let ii = 0; ii < views.length; ++ii) {
-        const view = views[ii];
-        const camera = view.camera;
-
-        view.updateCamera(camera, scene, mouseX, mouseY);
-
-        const left = Math.floor(windowWidth * view.left);
-        const bottom = Math.floor(windowHeight * view.bottom);
-        const width = Math.floor(windowWidth * view.width);
-        const height = Math.floor(windowHeight * view.height);
-
-        renderer.setViewport(left, bottom, width, height);
-        renderer.setScissor(left, bottom, width, height);
-        renderer.setScissorTest(true);
-        renderer.setClearColor(view.background);
-
-        camera.aspect = width / height;
-        camera.updateProjectionMatrix();
-
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgl_multisampled_renderbuffers.ts b/examples-testing/examples/webgl_multisampled_renderbuffers.ts
deleted file mode 100644
index df84fb144..000000000
--- a/examples-testing/examples/webgl_multisampled_renderbuffers.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, renderer, group, container;
-
-let composer1, composer2;
-
-const params = {
-    animate: true,
-};
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 10, 2000);
-    camera.position.z = 500;
-
-    const scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-    scene.fog = new THREE.Fog(0xcccccc, 100, 1500);
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x222222, 5);
-    hemiLight.position.set(1, 1, 1);
-    scene.add(hemiLight);
-
-    //
-
-    group = new THREE.Group();
-
-    const geometry = new THREE.SphereGeometry(10, 64, 40);
-    const material = new THREE.MeshLambertMaterial({
-        color: 0xee0808,
-        polygonOffset: true,
-        polygonOffsetFactor: 1, // positive value pushes polygon further away
-        polygonOffsetUnits: 1,
-    });
-    const material2 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
-
-    for (let i = 0; i < 50; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 600 - 300;
-        mesh.position.y = Math.random() * 600 - 300;
-        mesh.position.z = Math.random() * 600 - 300;
-        mesh.rotation.x = Math.random();
-        mesh.rotation.z = Math.random();
-        mesh.scale.setScalar(Math.random() * 5 + 5);
-        group.add(mesh);
-
-        const mesh2 = new THREE.Mesh(geometry, material2);
-        mesh2.position.copy(mesh.position);
-        mesh2.rotation.copy(mesh.rotation);
-        mesh2.scale.copy(mesh.scale);
-        group.add(mesh2);
-    }
-
-    scene.add(group);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const size = renderer.getDrawingBufferSize(new THREE.Vector2());
-    const renderTarget = new THREE.WebGLRenderTarget(size.width, size.height, {
-        samples: 4,
-        type: THREE.HalfFloatType,
-    });
-
-    const renderPass = new RenderPass(scene, camera);
-    const outputPass = new OutputPass();
-
-    //
-
-    composer1 = new EffectComposer(renderer);
-    composer1.addPass(renderPass);
-    composer1.addPass(outputPass);
-
-    //
-
-    composer2 = new EffectComposer(renderer, renderTarget);
-    composer2.addPass(renderPass);
-    composer2.addPass(outputPass);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'animate');
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-    composer1.setSize(container.offsetWidth, container.offsetHeight);
-    composer2.setSize(container.offsetWidth, container.offsetHeight);
-}
-
-function animate() {
-    const halfWidth = container.offsetWidth / 2;
-
-    if (params.animate) {
-        group.rotation.y += 0.002;
-    }
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, halfWidth - 1, container.offsetHeight);
-    composer1.render();
-
-    renderer.setScissor(halfWidth, 0, halfWidth, container.offsetHeight);
-    composer2.render();
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgl_panorama_cube.ts b/examples-testing/examples/webgl_panorama_cube.ts
deleted file mode 100644
index efd09cfc5..000000000
--- a/examples-testing/examples/webgl_panorama_cube.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, controls;
-let renderer;
-let scene;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 0.01;
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enablePan = false;
-    controls.enableDamping = true;
-    controls.rotateSpeed = -0.25;
-
-    const textures = getTexturesFromAtlasFile('textures/cube/sun_temple_stripe.jpg', 6);
-
-    const materials = [];
-
-    for (let i = 0; i < 6; i++) {
-        materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
-    }
-
-    const skyBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), materials);
-    skyBox.geometry.scale(1, 1, -1);
-    scene.add(skyBox);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
-    const textures = [];
-
-    for (let i = 0; i < tilesNum; i++) {
-        textures[i] = new THREE.Texture();
-    }
-
-    new THREE.ImageLoader().load(atlasImgUrl, image => {
-        let canvas, context;
-        const tileWidth = image.height;
-
-        for (let i = 0; i < textures.length; i++) {
-            canvas = document.createElement('canvas');
-            context = canvas.getContext('2d');
-            canvas.height = tileWidth;
-            canvas.width = tileWidth;
-            context.drawImage(image, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
-            textures[i].colorSpace = THREE.SRGBColorSpace;
-            textures[i].image = canvas;
-            textures[i].needsUpdate = true;
-        }
-    });
-
-    return textures;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update(); // required when damping is enabled
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_panorama_equirectangular.ts b/examples-testing/examples/webgl_panorama_equirectangular.ts
deleted file mode 100644
index 40796f6e2..000000000
--- a/examples-testing/examples/webgl_panorama_equirectangular.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-
-let isUserInteracting = false,
-    onPointerDownMouseX = 0,
-    onPointerDownMouseY = 0,
-    lon = 0,
-    onPointerDownLon = 0,
-    lat = 0,
-    onPointerDownLat = 0,
-    phi = 0,
-    theta = 0;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.SphereGeometry(500, 60, 40);
-    // invert the geometry on the x-axis so that all of the faces point inward
-    geometry.scale(-1, 1, 1);
-
-    const texture = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
-    texture.colorSpace = THREE.SRGBColorSpace;
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh = new THREE.Mesh(geometry, material);
-
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointerdown', onPointerDown);
-
-    document.addEventListener('wheel', onDocumentMouseWheel);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerDown(event) {
-    if (event.isPrimary === false) return;
-
-    isUserInteracting = true;
-
-    onPointerDownMouseX = event.clientX;
-    onPointerDownMouseY = event.clientY;
-
-    onPointerDownLon = lon;
-    onPointerDownLat = lat;
-
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    lon = (onPointerDownMouseX - event.clientX) * 0.1 + onPointerDownLon;
-    lat = (event.clientY - onPointerDownMouseY) * 0.1 + onPointerDownLat;
-}
-
-function onPointerUp() {
-    if (event.isPrimary === false) return;
-
-    isUserInteracting = false;
-
-    document.removeEventListener('pointermove', onPointerMove);
-    document.removeEventListener('pointerup', onPointerUp);
-}
-
-function onDocumentMouseWheel(event) {
-    const fov = camera.fov + event.deltaY * 0.05;
-
-    camera.fov = THREE.MathUtils.clamp(fov, 10, 75);
-
-    camera.updateProjectionMatrix();
-}
-
-function animate() {
-    if (isUserInteracting === false) {
-        lon += 0.1;
-    }
-
-    lat = Math.max(-85, Math.min(85, lat));
-    phi = THREE.MathUtils.degToRad(90 - lat);
-    theta = THREE.MathUtils.degToRad(lon);
-
-    const x = 500 * Math.sin(phi) * Math.cos(theta);
-    const y = 500 * Math.cos(phi);
-    const z = 500 * Math.sin(phi) * Math.sin(theta);
-
-    camera.lookAt(x, y, z);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_performance.ts b/examples-testing/examples/webgl_performance.ts
deleted file mode 100644
index 3700386a3..000000000
--- a/examples-testing/examples/webgl_performance.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(60, 60, 60);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-
-    renderer.setAnimationLoop(render);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.environment = texture;
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/');
-        loader.load('dungeon_warkarma.glb', async function (gltf) {
-            const model = gltf.scene;
-
-            // wait until the model can be added to the scene without blocking due to shader compilation
-
-            await renderer.compileAsync(model, camera, scene);
-
-            scene.add(model);
-        });
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 60;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_pmrem_test.ts b/examples-testing/examples/webgl_pmrem_test.ts
deleted file mode 100644
index b33e4e2f1..000000000
--- a/examples-testing/examples/webgl_pmrem_test.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, controls, renderer;
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-    const aspect = width / height;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-
-    // tonemapping
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(40, aspect, 1, 30);
-    updateCamera();
-    camera.position.set(0, 0, 16);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 4;
-    controls.maxDistance = 20;
-
-    // light
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 0); // set intensity to 0 to start
-    const x = 597;
-    const y = 213;
-    const theta = ((x + 0.5) * Math.PI) / 512;
-    const phi = ((y + 0.5) * Math.PI) / 512;
-
-    directionalLight.position.setFromSphericalCoords(100, -phi, Math.PI / 2 - theta);
-
-    scene.add(directionalLight);
-    // scene.add( new THREE.DirectionalLightHelper( directionalLight ) );
-
-    // The spot1Lux HDR environment map is expressed in nits (lux / sr). The directional light has units of lux,
-    // so to match a 1 lux light, we set a single pixel with a value equal to 1 divided by the solid
-    // angle of the pixel in steradians. This image is 1024 x 512,
-    // so the value is 1 / ( sin( phi ) * ( pi / 512 ) ^ 2 ) = 27,490 nits.
-
-    const gui = new GUI();
-    gui.add({ enabled: true }, 'enabled')
-        .name('PMREM')
-        .onChange(value => {
-            directionalLight.intensity = value ? 0 : 1;
-
-            scene.traverse(function (child) {
-                if (child.isMesh) {
-                    child.material.envMapIntensity = 1 - directionalLight.intensity;
-                }
-            });
-
-            render();
-        });
-}
-
-function createObjects() {
-    let radianceMap = null;
-    new RGBELoader()
-        // .setDataType( THREE.FloatType )
-        .setPath('textures/equirectangular/')
-        .load('spot1Lux.hdr', function (texture) {
-            radianceMap = pmremGenerator.fromEquirectangular(texture).texture;
-            pmremGenerator.dispose();
-
-            scene.background = radianceMap;
-
-            const geometry = new THREE.SphereGeometry(0.4, 32, 32);
-
-            for (let x = 0; x <= 10; x++) {
-                for (let y = 0; y <= 2; y++) {
-                    const material = new THREE.MeshPhysicalMaterial({
-                        roughness: x / 10,
-                        metalness: y < 1 ? 1 : 0,
-                        color: y < 2 ? 0xffffff : 0x000000,
-                        envMap: radianceMap,
-                        envMapIntensity: 1,
-                    });
-
-                    const mesh = new THREE.Mesh(geometry, material);
-                    mesh.position.x = x - 5;
-                    mesh.position.y = 1 - y;
-                    scene.add(mesh);
-                }
-            }
-
-            render();
-        });
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    pmremGenerator.compileEquirectangularShader();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    updateCamera();
-
-    renderer.setSize(width, height);
-
-    render();
-}
-
-function updateCamera() {
-    const horizontalFoV = 40;
-    const verticalFoV =
-        (2 * Math.atan(Math.tan(((horizontalFoV / 2) * Math.PI) / 180) / camera.aspect) * 180) / Math.PI;
-    camera.fov = verticalFoV;
-    camera.updateProjectionMatrix();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
-
-Promise.resolve().then(init).then(createObjects).then(render);
diff --git a/examples-testing/examples/webgl_points_billboards.ts b/examples-testing/examples/webgl_points_billboards.ts
deleted file mode 100644
index 24d4de1a9..000000000
--- a/examples-testing/examples/webgl_points_billboards.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats, material;
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 2, 2000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.FogExp2(0x000000, 0.001);
-
-    const geometry = new THREE.BufferGeometry();
-    const vertices = [];
-
-    const sprite = new THREE.TextureLoader().load('textures/sprites/disc.png');
-    sprite.colorSpace = THREE.SRGBColorSpace;
-
-    for (let i = 0; i < 10000; i++) {
-        const x = 2000 * Math.random() - 1000;
-        const y = 2000 * Math.random() - 1000;
-        const z = 2000 * Math.random() - 1000;
-
-        vertices.push(x, y, z);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    material = new THREE.PointsMaterial({
-        size: 35,
-        sizeAttenuation: true,
-        map: sprite,
-        alphaTest: 0.5,
-        transparent: true,
-    });
-    material.color.setHSL(1.0, 0.3, 0.7, THREE.SRGBColorSpace);
-
-    const particles = new THREE.Points(geometry, material);
-    scene.add(particles);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(material, 'sizeAttenuation').onChange(function () {
-        material.needsUpdate = true;
-    });
-
-    gui.open();
-
-    //
-
-    document.body.style.touchAction = 'none';
-    document.body.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.00005;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    const h = ((360 * (1.0 + time)) % 360) / 360;
-    material.color.setHSL(h, 0.5, 0.5);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_points_sprites.ts b/examples-testing/examples/webgl_points_sprites.ts
deleted file mode 100644
index 31b9e2ce1..000000000
--- a/examples-testing/examples/webgl_points_sprites.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, stats, parameters;
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-const materials = [];
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.FogExp2(0x000000, 0.0008);
-
-    const geometry = new THREE.BufferGeometry();
-    const vertices = [];
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const assignSRGB = texture => {
-        texture.colorSpace = THREE.SRGBColorSpace;
-    };
-
-    const sprite1 = textureLoader.load('textures/sprites/snowflake1.png', assignSRGB);
-    const sprite2 = textureLoader.load('textures/sprites/snowflake2.png', assignSRGB);
-    const sprite3 = textureLoader.load('textures/sprites/snowflake3.png', assignSRGB);
-    const sprite4 = textureLoader.load('textures/sprites/snowflake4.png', assignSRGB);
-    const sprite5 = textureLoader.load('textures/sprites/snowflake5.png', assignSRGB);
-
-    for (let i = 0; i < 10000; i++) {
-        const x = Math.random() * 2000 - 1000;
-        const y = Math.random() * 2000 - 1000;
-        const z = Math.random() * 2000 - 1000;
-
-        vertices.push(x, y, z);
-    }
-
-    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
-
-    parameters = [
-        [[1.0, 0.2, 0.5], sprite2, 20],
-        [[0.95, 0.1, 0.5], sprite3, 15],
-        [[0.9, 0.05, 0.5], sprite1, 10],
-        [[0.85, 0, 0.5], sprite5, 8],
-        [[0.8, 0, 0.5], sprite4, 5],
-    ];
-
-    for (let i = 0; i < parameters.length; i++) {
-        const color = parameters[i][0];
-        const sprite = parameters[i][1];
-        const size = parameters[i][2];
-
-        materials[i] = new THREE.PointsMaterial({
-            size: size,
-            map: sprite,
-            blending: THREE.AdditiveBlending,
-            depthTest: false,
-            transparent: true,
-        });
-        materials[i].color.setHSL(color[0], color[1], color[2], THREE.SRGBColorSpace);
-
-        const particles = new THREE.Points(geometry, materials[i]);
-
-        particles.rotation.x = Math.random() * 6;
-        particles.rotation.y = Math.random() * 6;
-        particles.rotation.z = Math.random() * 6;
-
-        scene.add(particles);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    const gui = new GUI();
-
-    const params = {
-        texture: true,
-    };
-
-    gui.add(params, 'texture').onChange(function (value) {
-        for (let i = 0; i < materials.length; i++) {
-            materials[i].map = value === true ? parameters[i][1] : null;
-            materials[i].needsUpdate = true;
-        }
-    });
-
-    gui.open();
-
-    document.body.style.touchAction = 'none';
-    document.body.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.00005;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0; i < scene.children.length; i++) {
-        const object = scene.children[i];
-
-        if (object instanceof THREE.Points) {
-            object.rotation.y = time * (i < 4 ? i + 1 : -(i + 1));
-        }
-    }
-
-    for (let i = 0; i < materials.length; i++) {
-        const color = parameters[i][0];
-
-        const h = ((360 * (color[0] + time)) % 360) / 360;
-        materials[i].color.setHSL(h, color[1], color[2], THREE.SRGBColorSpace);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_points_waves.ts b/examples-testing/examples/webgl_points_waves.ts
deleted file mode 100644
index 91986e9e9..000000000
--- a/examples-testing/examples/webgl_points_waves.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-const SEPARATION = 100,
-    AMOUNTX = 50,
-    AMOUNTY = 50;
-
-let container, stats;
-let camera, scene, renderer;
-
-let particles,
-    count = 0;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 1000;
-
-    scene = new THREE.Scene();
-
-    //
-
-    const numParticles = AMOUNTX * AMOUNTY;
-
-    const positions = new Float32Array(numParticles * 3);
-    const scales = new Float32Array(numParticles);
-
-    let i = 0,
-        j = 0;
-
-    for (let ix = 0; ix < AMOUNTX; ix++) {
-        for (let iy = 0; iy < AMOUNTY; iy++) {
-            positions[i] = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2; // x
-            positions[i + 1] = 0; // y
-            positions[i + 2] = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2; // z
-
-            scales[j] = 1;
-
-            i += 3;
-            j++;
-        }
-    }
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
-    geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            color: { value: new THREE.Color(0xffffff) },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-    });
-
-    //
-
-    particles = new THREE.Points(geometry, material);
-    scene.add(particles);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    container.style.touchAction = 'none';
-    container.addEventListener('pointermove', onPointerMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-    camera.lookAt(scene.position);
-
-    const positions = particles.geometry.attributes.position.array;
-    const scales = particles.geometry.attributes.scale.array;
-
-    let i = 0,
-        j = 0;
-
-    for (let ix = 0; ix < AMOUNTX; ix++) {
-        for (let iy = 0; iy < AMOUNTY; iy++) {
-            positions[i + 1] = Math.sin((ix + count) * 0.3) * 50 + Math.sin((iy + count) * 0.5) * 50;
-
-            scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 20 + (Math.sin((iy + count) * 0.5) + 1) * 20;
-
-            i += 3;
-            j++;
-        }
-    }
-
-    particles.geometry.attributes.position.needsUpdate = true;
-    particles.geometry.attributes.scale.needsUpdate = true;
-
-    renderer.render(scene, camera);
-
-    count += 0.1;
-}
diff --git a/examples-testing/examples/webgl_portal.ts b/examples-testing/examples/webgl_portal.ts
deleted file mode 100644
index 4bc59593f..000000000
--- a/examples-testing/examples/webgl_portal.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import * as THREE from 'three';
-
-import * as CameraUtils from 'three/addons/utils/CameraUtils.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-let cameraControls;
-
-let smallSphereOne, smallSphereTwo;
-
-let portalCamera,
-    leftPortal,
-    rightPortal,
-    leftPortalTexture,
-    reflectedPosition,
-    rightPortalTexture,
-    bottomLeftCorner,
-    bottomRightCorner,
-    topLeftCorner;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.localClippingEnabled = true;
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
-    camera.position.set(0, 75, 160);
-
-    cameraControls = new OrbitControls(camera, renderer.domElement);
-    cameraControls.target.set(0, 40, 0);
-    cameraControls.maxDistance = 400;
-    cameraControls.minDistance = 10;
-    cameraControls.update();
-
-    //
-
-    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
-
-    // bouncing icosphere
-    const portalPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0.0);
-    const geometry = new THREE.IcosahedronGeometry(5, 0);
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xffffff,
-        emissive: 0x333333,
-        flatShading: true,
-        clippingPlanes: [portalPlane],
-        clipShadows: true,
-    });
-    smallSphereOne = new THREE.Mesh(geometry, material);
-    scene.add(smallSphereOne);
-    smallSphereTwo = new THREE.Mesh(geometry, material);
-    scene.add(smallSphereTwo);
-
-    // portals
-    portalCamera = new THREE.PerspectiveCamera(45, 1.0, 0.1, 500.0);
-    scene.add(portalCamera);
-    //frustumHelper = new THREE.CameraHelper( portalCamera );
-    //scene.add( frustumHelper );
-    bottomLeftCorner = new THREE.Vector3();
-    bottomRightCorner = new THREE.Vector3();
-    topLeftCorner = new THREE.Vector3();
-    reflectedPosition = new THREE.Vector3();
-
-    leftPortalTexture = new THREE.WebGLRenderTarget(256, 256);
-    leftPortal = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ map: leftPortalTexture.texture }));
-    leftPortal.position.x = -30;
-    leftPortal.position.y = 20;
-    leftPortal.scale.set(0.35, 0.35, 0.35);
-    scene.add(leftPortal);
-
-    rightPortalTexture = new THREE.WebGLRenderTarget(256, 256);
-    rightPortal = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ map: rightPortalTexture.texture }));
-    rightPortal.position.x = 30;
-    rightPortal.position.y = 20;
-    rightPortal.scale.set(0.35, 0.35, 0.35);
-    scene.add(rightPortal);
-
-    // walls
-    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeTop.position.y = 100;
-    planeTop.rotateX(Math.PI / 2);
-    scene.add(planeTop);
-
-    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeBottom.rotateX(-Math.PI / 2);
-    scene.add(planeBottom);
-
-    const planeFront = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
-    planeFront.position.z = 50;
-    planeFront.position.y = 50;
-    planeFront.rotateY(Math.PI);
-    scene.add(planeFront);
-
-    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff7fff }));
-    planeBack.position.z = -50;
-    planeBack.position.y = 50;
-    //planeBack.rotateY( Math.PI );
-    scene.add(planeBack);
-
-    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
-    planeRight.position.x = 50;
-    planeRight.position.y = 50;
-    planeRight.rotateY(-Math.PI / 2);
-    scene.add(planeRight);
-
-    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
-    planeLeft.position.x = -50;
-    planeLeft.position.y = 50;
-    planeLeft.rotateY(Math.PI / 2);
-    scene.add(planeLeft);
-
-    // lights
-    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
-    mainLight.position.y = 60;
-    scene.add(mainLight);
-
-    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
-    greenLight.position.set(550, 50, 0);
-    scene.add(greenLight);
-
-    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
-    redLight.position.set(-550, 50, 0);
-    scene.add(redLight);
-
-    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
-    blueLight.position.set(0, 50, 550);
-    scene.add(blueLight);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function renderPortal(thisPortalMesh, otherPortalMesh, thisPortalTexture) {
-    // set the portal camera position to be reflected about the portal plane
-    thisPortalMesh.worldToLocal(reflectedPosition.copy(camera.position));
-    reflectedPosition.x *= -1.0;
-    reflectedPosition.z *= -1.0;
-    otherPortalMesh.localToWorld(reflectedPosition);
-    portalCamera.position.copy(reflectedPosition);
-
-    // grab the corners of the other portal
-    // - note: the portal is viewed backwards; flip the left/right coordinates
-    otherPortalMesh.localToWorld(bottomLeftCorner.set(50.05, -50.05, 0.0));
-    otherPortalMesh.localToWorld(bottomRightCorner.set(-50.05, -50.05, 0.0));
-    otherPortalMesh.localToWorld(topLeftCorner.set(50.05, 50.05, 0.0));
-    // set the projection matrix to encompass the portal's frame
-    CameraUtils.frameCorners(portalCamera, bottomLeftCorner, bottomRightCorner, topLeftCorner, false);
-
-    // render the portal
-    thisPortalTexture.texture.colorSpace = renderer.outputColorSpace;
-    renderer.setRenderTarget(thisPortalTexture);
-    renderer.state.buffers.depth.setMask(true); // make sure the depth buffer is writable so it can be properly cleared, see #18897
-    if (renderer.autoClear === false) renderer.clear();
-    thisPortalMesh.visible = false; // hide this portal from its own rendering
-    renderer.render(scene, portalCamera);
-    thisPortalMesh.visible = true; // re-enable this portal's visibility for general rendering
-}
-
-function animate() {
-    // move the bouncing sphere(s)
-    const timerOne = Date.now() * 0.01;
-    const timerTwo = timerOne + Math.PI * 10.0;
-
-    smallSphereOne.position.set(
-        Math.cos(timerOne * 0.1) * 30,
-        Math.abs(Math.cos(timerOne * 0.2)) * 20 + 5,
-        Math.sin(timerOne * 0.1) * 30,
-    );
-    smallSphereOne.rotation.y = Math.PI / 2 - timerOne * 0.1;
-    smallSphereOne.rotation.z = timerOne * 0.8;
-
-    smallSphereTwo.position.set(
-        Math.cos(timerTwo * 0.1) * 30,
-        Math.abs(Math.cos(timerTwo * 0.2)) * 20 + 5,
-        Math.sin(timerTwo * 0.1) * 30,
-    );
-    smallSphereTwo.rotation.y = Math.PI / 2 - timerTwo * 0.1;
-    smallSphereTwo.rotation.z = timerTwo * 0.8;
-
-    // save the original camera properties
-    const currentRenderTarget = renderer.getRenderTarget();
-    const currentXrEnabled = renderer.xr.enabled;
-    const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
-    renderer.xr.enabled = false; // Avoid camera modification
-    renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
-
-    // render the portal effect
-    renderPortal(leftPortal, rightPortal, leftPortalTexture);
-    renderPortal(rightPortal, leftPortal, rightPortalTexture);
-
-    // restore the original rendering properties
-    renderer.xr.enabled = currentXrEnabled;
-    renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
-    renderer.setRenderTarget(currentRenderTarget);
-
-    // render the main scene
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_postprocessing.ts b/examples-testing/examples/webgl_postprocessing.ts
deleted file mode 100644
index ecc9b28ee..000000000
--- a/examples-testing/examples/webgl_postprocessing.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-
-import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
-import { DotScreenShader } from 'three/addons/shaders/DotScreenShader.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, renderer, composer;
-let object;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    const scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 1000);
-
-    object = new THREE.Object3D();
-    scene.add(object);
-
-    const geometry = new THREE.SphereGeometry(1, 4, 4);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
-
-    for (let i = 0; i < 100; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
-        mesh.position.multiplyScalar(Math.random() * 400);
-        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
-        object.add(mesh);
-    }
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    composer.addPass(new RenderPass(scene, camera));
-
-    const effect1 = new ShaderPass(DotScreenShader);
-    effect1.uniforms['scale'].value = 4;
-    composer.addPass(effect1);
-
-    const effect2 = new ShaderPass(RGBShiftShader);
-    effect2.uniforms['amount'].value = 0.0015;
-    composer.addPass(effect2);
-
-    const effect3 = new OutputPass();
-    composer.addPass(effect3);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    object.rotation.x += 0.005;
-    object.rotation.y += 0.01;
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_advanced.ts b/examples-testing/examples/webgl_postprocessing_advanced.ts
deleted file mode 100644
index adaef6208..000000000
--- a/examples-testing/examples/webgl_postprocessing_advanced.ts
+++ /dev/null
@@ -1,304 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
-import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
-import { DotScreenPass } from 'three/addons/postprocessing/DotScreenPass.js';
-import { MaskPass, ClearMaskPass } from 'three/addons/postprocessing/MaskPass.js';
-import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
-
-import { BleachBypassShader } from 'three/addons/shaders/BleachBypassShader.js';
-import { ColorifyShader } from 'three/addons/shaders/ColorifyShader.js';
-import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';
-import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';
-import { SepiaShader } from 'three/addons/shaders/SepiaShader.js';
-import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
-import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let container, stats;
-
-let composerScene, composer1, composer2, composer3, composer4;
-
-let cameraOrtho, cameraPerspective, sceneModel, sceneBG, renderer, mesh, directionalLight;
-
-const width = window.innerWidth || 2;
-const height = window.innerHeight || 2;
-
-let halfWidth = width / 2;
-let halfHeight = height / 2;
-
-let quadBG, quadMask, renderScene;
-
-const delta = 0.01;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    cameraOrtho = new THREE.OrthographicCamera(-halfWidth, halfWidth, halfHeight, -halfHeight, -10000, 10000);
-    cameraOrtho.position.z = 100;
-
-    cameraPerspective = new THREE.PerspectiveCamera(50, width / height, 1, 10000);
-    cameraPerspective.position.z = 900;
-
-    //
-
-    sceneModel = new THREE.Scene();
-    sceneBG = new THREE.Scene();
-
-    //
-
-    directionalLight = new THREE.DirectionalLight(0xffffff, 3);
-    directionalLight.position.set(0, -0.1, 1).normalize();
-    sceneModel.add(directionalLight);
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/LeePerrySmith/LeePerrySmith.glb', function (gltf) {
-        createMesh(gltf.scene.children[0].geometry, sceneModel, 100);
-    });
-
-    //
-
-    const diffuseMap = new THREE.TextureLoader().load('textures/cube/SwedishRoyalCastle/pz.jpg');
-    diffuseMap.colorSpace = THREE.SRGBColorSpace;
-
-    const materialColor = new THREE.MeshBasicMaterial({
-        map: diffuseMap,
-        depthTest: false,
-    });
-
-    quadBG = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), materialColor);
-    quadBG.position.z = -500;
-    quadBG.scale.set(width, height, 1);
-    sceneBG.add(quadBG);
-
-    //
-
-    const sceneMask = new THREE.Scene();
-
-    quadMask = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ color: 0xffaa00 }));
-    quadMask.position.z = -300;
-    quadMask.scale.set(width / 2, height / 2, 1);
-    sceneMask.add(quadMask);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    //
-
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    const shaderBleach = BleachBypassShader;
-    const shaderSepia = SepiaShader;
-    const shaderVignette = VignetteShader;
-
-    const effectBleach = new ShaderPass(shaderBleach);
-    const effectSepia = new ShaderPass(shaderSepia);
-    const effectVignette = new ShaderPass(shaderVignette);
-    const gammaCorrection = new ShaderPass(GammaCorrectionShader);
-
-    effectBleach.uniforms['opacity'].value = 0.95;
-
-    effectSepia.uniforms['amount'].value = 0.9;
-
-    effectVignette.uniforms['offset'].value = 0.95;
-    effectVignette.uniforms['darkness'].value = 1.6;
-
-    const effectBloom = new BloomPass(0.5);
-    const effectFilm = new FilmPass(0.35);
-    const effectFilmBW = new FilmPass(0.35, true);
-    const effectDotScreen = new DotScreenPass(new THREE.Vector2(0, 0), 0.5, 0.8);
-
-    const effectHBlur = new ShaderPass(HorizontalBlurShader);
-    const effectVBlur = new ShaderPass(VerticalBlurShader);
-    effectHBlur.uniforms['h'].value = 2 / (width / 2);
-    effectVBlur.uniforms['v'].value = 2 / (height / 2);
-
-    const effectColorify1 = new ShaderPass(ColorifyShader);
-    const effectColorify2 = new ShaderPass(ColorifyShader);
-    effectColorify1.uniforms['color'] = new THREE.Uniform(new THREE.Color(1, 0.8, 0.8));
-    effectColorify2.uniforms['color'] = new THREE.Uniform(new THREE.Color(1, 0.75, 0.5));
-
-    const clearMask = new ClearMaskPass();
-    const renderMask = new MaskPass(sceneModel, cameraPerspective);
-    const renderMaskInverse = new MaskPass(sceneModel, cameraPerspective);
-
-    renderMaskInverse.inverse = true;
-
-    //
-
-    const rtParameters = {
-        stencilBuffer: true,
-    };
-
-    const rtWidth = width / 2;
-    const rtHeight = height / 2;
-
-    //
-
-    const renderBackground = new RenderPass(sceneBG, cameraOrtho);
-    const renderModel = new RenderPass(sceneModel, cameraPerspective);
-
-    renderModel.clear = false;
-
-    composerScene = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth * 2, rtHeight * 2, rtParameters));
-
-    composerScene.addPass(renderBackground);
-    composerScene.addPass(renderModel);
-    composerScene.addPass(renderMaskInverse);
-    composerScene.addPass(effectHBlur);
-    composerScene.addPass(effectVBlur);
-    composerScene.addPass(clearMask);
-
-    //
-
-    renderScene = new TexturePass(composerScene.renderTarget2.texture);
-
-    //
-
-    composer1 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
-
-    composer1.addPass(renderScene);
-    composer1.addPass(gammaCorrection);
-    composer1.addPass(effectFilmBW);
-    composer1.addPass(effectVignette);
-
-    //
-
-    composer2 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
-
-    composer2.addPass(renderScene);
-    composer2.addPass(gammaCorrection);
-    composer2.addPass(effectDotScreen);
-    composer2.addPass(renderMask);
-    composer2.addPass(effectColorify1);
-    composer2.addPass(clearMask);
-    composer2.addPass(renderMaskInverse);
-    composer2.addPass(effectColorify2);
-    composer2.addPass(clearMask);
-    composer2.addPass(effectVignette);
-
-    //
-
-    composer3 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
-
-    composer3.addPass(renderScene);
-    composer3.addPass(gammaCorrection);
-    composer3.addPass(effectSepia);
-    composer3.addPass(effectFilm);
-    composer3.addPass(effectVignette);
-
-    //
-
-    composer4 = new EffectComposer(renderer, new THREE.WebGLRenderTarget(rtWidth, rtHeight, rtParameters));
-
-    composer4.addPass(renderScene);
-    composer4.addPass(gammaCorrection);
-    composer4.addPass(effectBloom);
-    composer4.addPass(effectFilm);
-    composer4.addPass(effectBleach);
-    composer4.addPass(effectVignette);
-
-    renderScene.uniforms['tDiffuse'].value = composerScene.renderTarget2.texture;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    halfWidth = window.innerWidth / 2;
-    halfHeight = window.innerHeight / 2;
-
-    cameraPerspective.aspect = window.innerWidth / window.innerHeight;
-    cameraPerspective.updateProjectionMatrix();
-
-    cameraOrtho.left = -halfWidth;
-    cameraOrtho.right = halfWidth;
-    cameraOrtho.top = halfHeight;
-    cameraOrtho.bottom = -halfHeight;
-
-    cameraOrtho.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    composerScene.setSize(halfWidth * 2, halfHeight * 2);
-
-    composer1.setSize(halfWidth, halfHeight);
-    composer2.setSize(halfWidth, halfHeight);
-    composer3.setSize(halfWidth, halfHeight);
-    composer4.setSize(halfWidth, halfHeight);
-
-    renderScene.uniforms['tDiffuse'].value = composerScene.renderTarget2.texture;
-
-    quadBG.scale.set(window.innerWidth, window.innerHeight, 1);
-    quadMask.scale.set(window.innerWidth / 2, window.innerHeight / 2, 1);
-}
-
-function createMesh(geometry, scene, scale) {
-    const diffuseMap = new THREE.TextureLoader().load('models/gltf/LeePerrySmith/Map-COL.jpg');
-    diffuseMap.colorSpace = THREE.SRGBColorSpace;
-
-    const mat2 = new THREE.MeshPhongMaterial({
-        color: 0xcbcbcb,
-        specular: 0x080808,
-        shininess: 20,
-        map: diffuseMap,
-        normalMap: new THREE.TextureLoader().load('models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg'),
-        normalScale: new THREE.Vector2(0.75, 0.75),
-    });
-
-    mesh = new THREE.Mesh(geometry, mat2);
-    mesh.position.set(0, -50, 0);
-    mesh.scale.set(scale, scale, scale);
-
-    scene.add(mesh);
-}
-
-//
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    const time = Date.now() * 0.0004;
-
-    if (mesh) mesh.rotation.y = -time;
-
-    renderer.setViewport(0, 0, halfWidth, halfHeight);
-    composerScene.render(delta);
-
-    renderer.setViewport(0, 0, halfWidth, halfHeight);
-    composer1.render(delta);
-
-    renderer.setViewport(halfWidth, 0, halfWidth, halfHeight);
-    composer2.render(delta);
-
-    renderer.setViewport(0, halfHeight, halfWidth, halfHeight);
-    composer3.render(delta);
-
-    renderer.setViewport(halfWidth, halfHeight, halfWidth, halfHeight);
-    composer4.render(delta);
-}
diff --git a/examples-testing/examples/webgl_postprocessing_afterimage.ts b/examples-testing/examples/webgl_postprocessing_afterimage.ts
deleted file mode 100644
index 508f90b89..000000000
--- a/examples-testing/examples/webgl_postprocessing_afterimage.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, composer;
-let mesh;
-
-let afterimagePass;
-
-const params = {
-    enable: true,
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 1000);
-
-    const geometry = new THREE.BoxGeometry(150, 150, 150, 2, 2, 2);
-    const material = new THREE.MeshNormalMaterial();
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    composer.addPass(new RenderPass(scene, camera));
-
-    afterimagePass = new AfterimagePass();
-    composer.addPass(afterimagePass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI({ title: 'Damp setting' });
-    gui.add(afterimagePass.uniforms['damp'], 'value', 0, 1).step(0.001);
-    gui.add(params, 'enable');
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.rotation.x += 0.005;
-    mesh.rotation.y += 0.01;
-
-    afterimagePass.enabled = params.enable;
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_backgrounds.ts b/examples-testing/examples/webgl_postprocessing_backgrounds.ts
deleted file mode 100644
index 57a6a2dbd..000000000
--- a/examples-testing/examples/webgl_postprocessing_backgrounds.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
-import { CubeTexturePass } from 'three/addons/postprocessing/CubeTexturePass.js';
-import { ClearPass } from 'three/addons/postprocessing/ClearPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let scene, renderer, composer;
-let clearPass, texturePass, renderPass;
-let cameraP, cubeTexturePassP;
-let gui, stats;
-
-const params = {
-    clearPass: true,
-    clearColor: 'white',
-    clearAlpha: 1.0,
-
-    texturePass: true,
-    texturePassOpacity: 1.0,
-
-    cubeTexturePass: true,
-    cubeTexturePassOpacity: 1.0,
-
-    renderPass: true,
-};
-
-init();
-
-clearGui();
-
-function clearGui() {
-    if (gui) gui.destroy();
-
-    gui = new GUI();
-
-    gui.add(params, 'clearPass');
-    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
-    gui.add(params, 'clearAlpha', 0, 1);
-
-    gui.add(params, 'texturePass');
-    gui.add(params, 'texturePassOpacity', 0, 1);
-
-    gui.add(params, 'cubeTexturePass');
-    gui.add(params, 'cubeTexturePassOpacity', 0, 1);
-
-    gui.add(params, 'renderPass');
-
-    gui.open();
-}
-
-function init() {
-    const container = document.getElementById('container');
-
-    const width = window.innerWidth || 1;
-    const height = window.innerHeight || 1;
-    const aspect = width / height;
-    const devicePixelRatio = window.devicePixelRatio || 1;
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    cameraP = new THREE.PerspectiveCamera(65, aspect, 1, 10);
-    cameraP.position.z = 7;
-
-    scene = new THREE.Scene();
-
-    const group = new THREE.Group();
-    scene.add(group);
-
-    const light = new THREE.PointLight(0xefffef, 500);
-    light.position.z = 10;
-    light.position.y = -10;
-    light.position.x = -10;
-    scene.add(light);
-
-    const light2 = new THREE.PointLight(0xffefef, 500);
-    light2.position.z = 10;
-    light2.position.x = -10;
-    light2.position.y = 10;
-    scene.add(light2);
-
-    const light3 = new THREE.PointLight(0xefefff, 500);
-    light3.position.z = 10;
-    light3.position.x = 10;
-    light3.position.y = -10;
-    scene.add(light3);
-
-    const geometry = new THREE.SphereGeometry(1, 48, 24);
-
-    const material = new THREE.MeshStandardMaterial();
-    material.roughness = 0.5 * Math.random() + 0.25;
-    material.metalness = 0;
-    material.color.setHSL(Math.random(), 1.0, 0.3);
-
-    const mesh = new THREE.Mesh(geometry, material);
-    group.add(mesh);
-
-    // postprocessing
-
-    const genCubeUrls = function (prefix, postfix) {
-        return [
-            prefix + 'px' + postfix,
-            prefix + 'nx' + postfix,
-            prefix + 'py' + postfix,
-            prefix + 'ny' + postfix,
-            prefix + 'pz' + postfix,
-            prefix + 'nz' + postfix,
-        ];
-    };
-
-    composer = new EffectComposer(renderer);
-
-    clearPass = new ClearPass(params.clearColor, params.clearAlpha);
-    composer.addPass(clearPass);
-
-    texturePass = new TexturePass();
-    composer.addPass(texturePass);
-
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
-        map.colorSpace = THREE.SRGBColorSpace;
-        texturePass.map = map;
-    });
-
-    cubeTexturePassP = null;
-
-    const ldrUrls = genCubeUrls('textures/cube/pisa/', '.png');
-    new THREE.CubeTextureLoader().load(ldrUrls, function (ldrCubeMap) {
-        cubeTexturePassP = new CubeTexturePass(cameraP, ldrCubeMap);
-        composer.insertPass(cubeTexturePassP, 2);
-    });
-
-    renderPass = new RenderPass(scene, cameraP);
-    renderPass.clear = false;
-    composer.addPass(renderPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    const controls = new OrbitControls(cameraP, renderer.domElement);
-    controls.enableZoom = false;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-    const aspect = width / height;
-
-    cameraP.aspect = aspect;
-    cameraP.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-
-    cameraP.updateMatrixWorld(true);
-
-    let newColor = clearPass.clearColor;
-
-    switch (params.clearColor) {
-        case 'blue':
-            newColor = 0x0000ff;
-            break;
-        case 'red':
-            newColor = 0xff0000;
-            break;
-        case 'green':
-            newColor = 0x00ff00;
-            break;
-        case 'white':
-            newColor = 0xffffff;
-            break;
-        case 'black':
-            newColor = 0x000000;
-            break;
-    }
-
-    clearPass.enabled = params.clearPass;
-    clearPass.clearColor = newColor;
-    clearPass.clearAlpha = params.clearAlpha;
-
-    texturePass.enabled = params.texturePass;
-    texturePass.opacity = params.texturePassOpacity;
-
-    if (cubeTexturePassP !== null) {
-        cubeTexturePassP.enabled = params.cubeTexturePass;
-        cubeTexturePassP.opacity = params.cubeTexturePassOpacity;
-    }
-
-    renderPass.enabled = params.renderPass;
-
-    composer.render();
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_fxaa.ts b/examples-testing/examples/webgl_postprocessing_fxaa.ts
deleted file mode 100644
index 55745f88e..000000000
--- a/examples-testing/examples/webgl_postprocessing_fxaa.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
-
-let camera, scene, renderer, clock, group, container;
-
-let composer1, composer2, fxaaPass;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, container.offsetWidth / container.offsetHeight, 1, 2000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d);
-    hemiLight.position.set(0, 1000, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(-3000, 1000, -1000);
-    scene.add(dirLight);
-
-    //
-
-    group = new THREE.Group();
-
-    const geometry = new THREE.TetrahedronGeometry(10);
-    const material = new THREE.MeshStandardMaterial({ color: 0xf73232, flatShading: true });
-
-    for (let i = 0; i < 100; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = Math.random() * 500 - 250;
-        mesh.position.y = Math.random() * 500 - 250;
-        mesh.position.z = Math.random() * 500 - 250;
-
-        mesh.scale.setScalar(Math.random() * 2 + 1);
-
-        mesh.rotation.x = Math.random() * Math.PI;
-        mesh.rotation.y = Math.random() * Math.PI;
-        mesh.rotation.z = Math.random() * Math.PI;
-
-        group.add(mesh);
-    }
-
-    scene.add(group);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const renderPass = new RenderPass(scene, camera);
-    renderPass.clearAlpha = 0;
-
-    //
-
-    fxaaPass = new ShaderPass(FXAAShader);
-
-    const outputPass = new OutputPass();
-
-    composer1 = new EffectComposer(renderer);
-    composer1.addPass(renderPass);
-    composer1.addPass(outputPass);
-
-    //
-
-    const pixelRatio = renderer.getPixelRatio();
-
-    fxaaPass.material.uniforms['resolution'].value.x = 1 / (container.offsetWidth * pixelRatio);
-    fxaaPass.material.uniforms['resolution'].value.y = 1 / (container.offsetHeight * pixelRatio);
-
-    composer2 = new EffectComposer(renderer);
-    composer2.addPass(renderPass);
-    composer2.addPass(outputPass);
-
-    // FXAA is engineered to be applied towards the end of engine post processing after conversion to low dynamic range and conversion to the sRGB color space for display.
-
-    composer2.addPass(fxaaPass);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = container.offsetWidth / container.offsetHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(container.offsetWidth, container.offsetHeight);
-    composer1.setSize(container.offsetWidth, container.offsetHeight);
-    composer2.setSize(container.offsetWidth, container.offsetHeight);
-
-    const pixelRatio = renderer.getPixelRatio();
-
-    fxaaPass.material.uniforms['resolution'].value.x = 1 / (container.offsetWidth * pixelRatio);
-    fxaaPass.material.uniforms['resolution'].value.y = 1 / (container.offsetHeight * pixelRatio);
-}
-
-function animate() {
-    const halfWidth = container.offsetWidth / 2;
-
-    group.rotation.y += clock.getDelta() * 0.1;
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, halfWidth - 1, container.offsetHeight);
-    composer1.render();
-
-    renderer.setScissor(halfWidth, 0, halfWidth, container.offsetHeight);
-    composer2.render();
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgl_postprocessing_glitch.ts b/examples-testing/examples/webgl_postprocessing_glitch.ts
deleted file mode 100644
index f846c0ce6..000000000
--- a/examples-testing/examples/webgl_postprocessing_glitch.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, composer;
-let object, light;
-
-let glitchPass;
-
-const button = document.querySelector('#startButton');
-button.addEventListener('click', function () {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    init();
-});
-
-function updateOptions() {
-    const wildGlitch = document.getElementById('wildGlitch');
-    glitchPass.goWild = wildGlitch.checked;
-}
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 1000);
-
-    object = new THREE.Object3D();
-    scene.add(object);
-
-    const geometry = new THREE.SphereGeometry(1, 4, 4);
-
-    for (let i = 0; i < 100; i++) {
-        const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random(), flatShading: true });
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
-        mesh.position.multiplyScalar(Math.random() * 400);
-        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
-        object.add(mesh);
-    }
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    composer.addPass(new RenderPass(scene, camera));
-
-    glitchPass = new GlitchPass();
-    composer.addPass(glitchPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    const wildGlitchOption = document.getElementById('wildGlitch');
-    wildGlitchOption.addEventListener('change', updateOptions);
-
-    updateOptions();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    object.rotation.x += 0.005;
-    object.rotation.y += 0.01;
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_godrays.ts b/examples-testing/examples/webgl_postprocessing_godrays.ts
deleted file mode 100644
index fb7604411..000000000
--- a/examples-testing/examples/webgl_postprocessing_godrays.ts
+++ /dev/null
@@ -1,347 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import {
-    GodRaysFakeSunShader,
-    GodRaysDepthMaskShader,
-    GodRaysCombineShader,
-    GodRaysGenerateShader,
-} from 'three/addons/shaders/GodRaysShader.js';
-
-let container, stats;
-let camera, scene, renderer, materialDepth;
-
-let sphereMesh;
-
-const sunPosition = new THREE.Vector3(0, 1000, -1000);
-const clipPosition = new THREE.Vector4();
-const screenSpacePosition = new THREE.Vector3();
-
-const postprocessing = { enabled: true };
-
-const orbitRadius = 200;
-
-const bgColor = 0x000511;
-const sunColor = 0xffee00;
-
-// Use a smaller size for some of the god-ray render targets for better performance.
-const godrayRenderTargetResolutionMultiplier = 1.0 / 4.0;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 3000);
-    camera.position.z = 200;
-
-    scene = new THREE.Scene();
-
-    //
-
-    materialDepth = new THREE.MeshDepthMaterial();
-
-    // tree
-
-    const loader = new OBJLoader();
-    loader.load('models/obj/tree.obj', function (object) {
-        object.position.set(0, -150, -150);
-        object.scale.multiplyScalar(400);
-        scene.add(object);
-    });
-
-    // sphere
-
-    const geo = new THREE.SphereGeometry(1, 20, 10);
-    sphereMesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color: 0x000000 }));
-    sphereMesh.scale.multiplyScalar(20);
-    scene.add(sphereMesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0xffffff);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    renderer.autoClear = false;
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 50;
-    controls.maxDistance = 500;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    initPostprocessing(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function onWindowResize() {
-    const renderTargetWidth = window.innerWidth;
-    const renderTargetHeight = window.innerHeight;
-
-    camera.aspect = renderTargetWidth / renderTargetHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(renderTargetWidth, renderTargetHeight);
-    postprocessing.rtTextureColors.setSize(renderTargetWidth, renderTargetHeight);
-    postprocessing.rtTextureDepth.setSize(renderTargetWidth, renderTargetHeight);
-    postprocessing.rtTextureDepthMask.setSize(renderTargetWidth, renderTargetHeight);
-
-    const adjustedWidth = renderTargetWidth * godrayRenderTargetResolutionMultiplier;
-    const adjustedHeight = renderTargetHeight * godrayRenderTargetResolutionMultiplier;
-    postprocessing.rtTextureGodRays1.setSize(adjustedWidth, adjustedHeight);
-    postprocessing.rtTextureGodRays2.setSize(adjustedWidth, adjustedHeight);
-}
-
-function initPostprocessing(renderTargetWidth, renderTargetHeight) {
-    postprocessing.scene = new THREE.Scene();
-
-    postprocessing.camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, -10000, 10000);
-    postprocessing.camera.position.z = 100;
-
-    postprocessing.scene.add(postprocessing.camera);
-
-    postprocessing.rtTextureColors = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
-        type: THREE.HalfFloatType,
-    });
-
-    // Switching the depth formats to luminance from rgb doesn't seem to work. I didn't
-    // investigate further for now.
-    // pars.format = LuminanceFormat;
-
-    // I would have this quarter size and use it as one of the ping-pong render
-    // targets but the aliasing causes some temporal flickering
-
-    postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
-        type: THREE.HalfFloatType,
-    });
-    postprocessing.rtTextureDepthMask = new THREE.WebGLRenderTarget(renderTargetWidth, renderTargetHeight, {
-        type: THREE.HalfFloatType,
-    });
-
-    // The ping-pong render targets can use an adjusted resolution to minimize cost
-
-    const adjustedWidth = renderTargetWidth * godrayRenderTargetResolutionMultiplier;
-    const adjustedHeight = renderTargetHeight * godrayRenderTargetResolutionMultiplier;
-    postprocessing.rtTextureGodRays1 = new THREE.WebGLRenderTarget(adjustedWidth, adjustedHeight, {
-        type: THREE.HalfFloatType,
-    });
-    postprocessing.rtTextureGodRays2 = new THREE.WebGLRenderTarget(adjustedWidth, adjustedHeight, {
-        type: THREE.HalfFloatType,
-    });
-
-    // god-ray shaders
-
-    const godraysMaskShader = GodRaysDepthMaskShader;
-    postprocessing.godrayMaskUniforms = THREE.UniformsUtils.clone(godraysMaskShader.uniforms);
-    postprocessing.materialGodraysDepthMask = new THREE.ShaderMaterial({
-        uniforms: postprocessing.godrayMaskUniforms,
-        vertexShader: godraysMaskShader.vertexShader,
-        fragmentShader: godraysMaskShader.fragmentShader,
-    });
-
-    const godraysGenShader = GodRaysGenerateShader;
-    postprocessing.godrayGenUniforms = THREE.UniformsUtils.clone(godraysGenShader.uniforms);
-    postprocessing.materialGodraysGenerate = new THREE.ShaderMaterial({
-        uniforms: postprocessing.godrayGenUniforms,
-        vertexShader: godraysGenShader.vertexShader,
-        fragmentShader: godraysGenShader.fragmentShader,
-    });
-
-    const godraysCombineShader = GodRaysCombineShader;
-    postprocessing.godrayCombineUniforms = THREE.UniformsUtils.clone(godraysCombineShader.uniforms);
-    postprocessing.materialGodraysCombine = new THREE.ShaderMaterial({
-        uniforms: postprocessing.godrayCombineUniforms,
-        vertexShader: godraysCombineShader.vertexShader,
-        fragmentShader: godraysCombineShader.fragmentShader,
-    });
-
-    const godraysFakeSunShader = GodRaysFakeSunShader;
-    postprocessing.godraysFakeSunUniforms = THREE.UniformsUtils.clone(godraysFakeSunShader.uniforms);
-    postprocessing.materialGodraysFakeSun = new THREE.ShaderMaterial({
-        uniforms: postprocessing.godraysFakeSunUniforms,
-        vertexShader: godraysFakeSunShader.vertexShader,
-        fragmentShader: godraysFakeSunShader.fragmentShader,
-    });
-
-    postprocessing.godraysFakeSunUniforms.bgColor.value.setHex(bgColor);
-    postprocessing.godraysFakeSunUniforms.sunColor.value.setHex(sunColor);
-
-    postprocessing.godrayCombineUniforms.fGodRayIntensity.value = 0.75;
-
-    postprocessing.quad = new THREE.Mesh(new THREE.PlaneGeometry(1.0, 1.0), postprocessing.materialGodraysGenerate);
-    postprocessing.quad.position.z = -9900;
-    postprocessing.scene.add(postprocessing.quad);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function getStepSize(filterLen, tapsPerPass, pass) {
-    return filterLen * Math.pow(tapsPerPass, -pass);
-}
-
-function filterGodRays(inputTex, renderTarget, stepSize) {
-    postprocessing.scene.overrideMaterial = postprocessing.materialGodraysGenerate;
-
-    postprocessing.godrayGenUniforms['fStepSize'].value = stepSize;
-    postprocessing.godrayGenUniforms['tInput'].value = inputTex;
-
-    renderer.setRenderTarget(renderTarget);
-    renderer.render(postprocessing.scene, postprocessing.camera);
-    postprocessing.scene.overrideMaterial = null;
-}
-
-function render() {
-    const time = Date.now() / 4000;
-
-    sphereMesh.position.x = orbitRadius * Math.cos(time);
-    sphereMesh.position.z = orbitRadius * Math.sin(time) - 100;
-
-    if (postprocessing.enabled) {
-        clipPosition.x = sunPosition.x;
-        clipPosition.y = sunPosition.y;
-        clipPosition.z = sunPosition.z;
-        clipPosition.w = 1;
-
-        clipPosition.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
-
-        // perspective divide (produce NDC space)
-
-        clipPosition.x /= clipPosition.w;
-        clipPosition.y /= clipPosition.w;
-
-        screenSpacePosition.x = (clipPosition.x + 1) / 2; // transform from [-1,1] to [0,1]
-        screenSpacePosition.y = (clipPosition.y + 1) / 2; // transform from [-1,1] to [0,1]
-        screenSpacePosition.z = clipPosition.z; // needs to stay in clip space for visibilty checks
-
-        // Give it to the god-ray and sun shaders
-
-        postprocessing.godrayGenUniforms['vSunPositionScreenSpace'].value.copy(screenSpacePosition);
-        postprocessing.godraysFakeSunUniforms['vSunPositionScreenSpace'].value.copy(screenSpacePosition);
-
-        // -- Draw sky and sun --
-
-        // Clear colors and depths, will clear to sky color
-
-        renderer.setRenderTarget(postprocessing.rtTextureColors);
-        renderer.clear(true, true, false);
-
-        // Sun render. Runs a shader that gives a brightness based on the screen
-        // space distance to the sun. Not very efficient, so i make a scissor
-        // rectangle around the suns position to avoid rendering surrounding pixels.
-
-        const sunsqH = 0.74 * window.innerHeight; // 0.74 depends on extent of sun from shader
-        const sunsqW = 0.74 * window.innerHeight; // both depend on height because sun is aspect-corrected
-
-        screenSpacePosition.x *= window.innerWidth;
-        screenSpacePosition.y *= window.innerHeight;
-
-        renderer.setScissor(screenSpacePosition.x - sunsqW / 2, screenSpacePosition.y - sunsqH / 2, sunsqW, sunsqH);
-        renderer.setScissorTest(true);
-
-        postprocessing.godraysFakeSunUniforms['fAspect'].value = window.innerWidth / window.innerHeight;
-
-        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysFakeSun;
-        renderer.setRenderTarget(postprocessing.rtTextureColors);
-        renderer.render(postprocessing.scene, postprocessing.camera);
-
-        renderer.setScissorTest(false);
-
-        // -- Draw scene objects --
-
-        // Colors
-
-        scene.overrideMaterial = null;
-        renderer.setRenderTarget(postprocessing.rtTextureColors);
-        renderer.render(scene, camera);
-
-        // Depth
-
-        scene.overrideMaterial = materialDepth;
-        renderer.setRenderTarget(postprocessing.rtTextureDepth);
-        renderer.clear();
-        renderer.render(scene, camera);
-
-        //
-
-        postprocessing.godrayMaskUniforms['tInput'].value = postprocessing.rtTextureDepth.texture;
-
-        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysDepthMask;
-        renderer.setRenderTarget(postprocessing.rtTextureDepthMask);
-        renderer.render(postprocessing.scene, postprocessing.camera);
-
-        // -- Render god-rays --
-
-        // Maximum length of god-rays (in texture space [0,1]X[0,1])
-
-        const filterLen = 1.0;
-
-        // Samples taken by filter
-
-        const TAPS_PER_PASS = 6.0;
-
-        // Pass order could equivalently be 3,2,1 (instead of 1,2,3), which
-        // would start with a small filter support and grow to large. however
-        // the large-to-small order produces less objectionable aliasing artifacts that
-        // appear as a glimmer along the length of the beams
-
-        // pass 1 - render into first ping-pong target
-        filterGodRays(
-            postprocessing.rtTextureDepthMask.texture,
-            postprocessing.rtTextureGodRays2,
-            getStepSize(filterLen, TAPS_PER_PASS, 1.0),
-        );
-
-        // pass 2 - render into second ping-pong target
-        filterGodRays(
-            postprocessing.rtTextureGodRays2.texture,
-            postprocessing.rtTextureGodRays1,
-            getStepSize(filterLen, TAPS_PER_PASS, 2.0),
-        );
-
-        // pass 3 - 1st RT
-        filterGodRays(
-            postprocessing.rtTextureGodRays1.texture,
-            postprocessing.rtTextureGodRays2,
-            getStepSize(filterLen, TAPS_PER_PASS, 3.0),
-        );
-
-        // final pass - composite god-rays onto colors
-
-        postprocessing.godrayCombineUniforms['tColors'].value = postprocessing.rtTextureColors.texture;
-        postprocessing.godrayCombineUniforms['tGodRays'].value = postprocessing.rtTextureGodRays2.texture;
-
-        postprocessing.scene.overrideMaterial = postprocessing.materialGodraysCombine;
-
-        renderer.setRenderTarget(null);
-        renderer.render(postprocessing.scene, postprocessing.camera);
-        postprocessing.scene.overrideMaterial = null;
-    } else {
-        renderer.setRenderTarget(null);
-        renderer.clear();
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgl_postprocessing_gtao.ts b/examples-testing/examples/webgl_postprocessing_gtao.ts
deleted file mode 100644
index 4f16d1554..000000000
--- a/examples-testing/examples/webgl_postprocessing_gtao.ts
+++ /dev/null
@@ -1,215 +0,0 @@
-import * as THREE from 'three';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, composer, controls, clock, stats, mixer;
-
-init();
-
-function init() {
-    const dracoLoader = new DRACOLoader();
-    dracoLoader.setDecoderPath('jsm/libs/draco/');
-    dracoLoader.setDecoderConfig({ type: 'js' });
-    const loader = new GLTFLoader();
-    loader.setDRACOLoader(dracoLoader);
-    loader.setPath('models/gltf/');
-
-    clock = new THREE.Clock();
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xbfe3dd);
-    scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(5, 2, 8);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0.5, 0);
-    controls.update();
-    controls.enablePan = false;
-    controls.enableDamping = true;
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    composer = new EffectComposer(renderer);
-
-    const renderPass = new RenderPass(scene, camera);
-    composer.addPass(renderPass);
-
-    const gtaoPass = new GTAOPass(scene, camera, width, height);
-    gtaoPass.output = GTAOPass.OUTPUT.Denoise;
-    composer.addPass(gtaoPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    //
-
-    loader.load(
-        'LittlestTokyo.glb',
-        gltf => {
-            const model = gltf.scene;
-            model.position.set(1, 1, 0);
-            model.scale.set(0.01, 0.01, 0.01);
-            scene.add(model);
-
-            mixer = new THREE.AnimationMixer(model);
-            mixer.clipAction(gltf.animations[0]).play();
-
-            const box = new THREE.Box3().setFromObject(scene);
-            gtaoPass.setSceneClipBox(box);
-        },
-        undefined,
-        e => console.error(e),
-    );
-
-    // Init gui
-    const gui = new GUI();
-
-    gui.add(gtaoPass, 'output', {
-        Default: GTAOPass.OUTPUT.Default,
-        Diffuse: GTAOPass.OUTPUT.Diffuse,
-        'AO Only': GTAOPass.OUTPUT.AO,
-        'AO Only + Denoise': GTAOPass.OUTPUT.Denoise,
-        Depth: GTAOPass.OUTPUT.Depth,
-        Normal: GTAOPass.OUTPUT.Normal,
-    }).onChange(function (value) {
-        gtaoPass.output = value;
-    });
-
-    const aoParameters = {
-        radius: 0.25,
-        distanceExponent: 1,
-        thickness: 1,
-        scale: 1,
-        samples: 16,
-        distanceFallOff: 1,
-        screenSpaceRadius: false,
-    };
-    const pdParameters = {
-        lumaPhi: 10,
-        depthPhi: 2,
-        normalPhi: 3,
-        radius: 4,
-        radiusExponent: 1,
-        rings: 2,
-        samples: 16,
-    };
-    gtaoPass.updateGtaoMaterial(aoParameters);
-    gtaoPass.updatePdMaterial(pdParameters);
-    gui.add(gtaoPass, 'blendIntensity').min(0).max(1).step(0.01);
-    gui.add(aoParameters, 'radius')
-        .min(0.01)
-        .max(1)
-        .step(0.01)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'distanceExponent')
-        .min(1)
-        .max(4)
-        .step(0.01)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'thickness')
-        .min(0.01)
-        .max(10)
-        .step(0.01)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'distanceFallOff')
-        .min(0)
-        .max(1)
-        .step(0.01)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'scale')
-        .min(0.01)
-        .max(2.0)
-        .step(0.01)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'samples')
-        .min(2)
-        .max(32)
-        .step(1)
-        .onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(aoParameters, 'screenSpaceRadius').onChange(() => gtaoPass.updateGtaoMaterial(aoParameters));
-    gui.add(pdParameters, 'lumaPhi')
-        .min(0)
-        .max(20)
-        .step(0.01)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'depthPhi')
-        .min(0.01)
-        .max(20)
-        .step(0.01)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'normalPhi')
-        .min(0.01)
-        .max(20)
-        .step(0.01)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'radius')
-        .min(0)
-        .max(32)
-        .step(1)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'radiusExponent')
-        .min(0.1)
-        .max(4)
-        .step(0.1)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'rings')
-        .min(1)
-        .max(16)
-        .step(0.125)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-    gui.add(pdParameters, 'samples')
-        .min(2)
-        .max(32)
-        .step(1)
-        .onChange(() => gtaoPass.updatePdMaterial(pdParameters));
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) {
-        mixer.update(delta);
-    }
-
-    controls.update();
-
-    stats.begin();
-    composer.render();
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_masking.ts b/examples-testing/examples/webgl_postprocessing_masking.ts
deleted file mode 100644
index f6e7310bf..000000000
--- a/examples-testing/examples/webgl_postprocessing_masking.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { TexturePass } from 'three/addons/postprocessing/TexturePass.js';
-import { ClearPass } from 'three/addons/postprocessing/ClearPass.js';
-import { MaskPass, ClearMaskPass } from 'three/addons/postprocessing/MaskPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, composer, renderer;
-let box, torus;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 10;
-
-    const scene1 = new THREE.Scene();
-    const scene2 = new THREE.Scene();
-
-    box = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 4));
-    scene1.add(box);
-
-    torus = new THREE.Mesh(new THREE.TorusGeometry(3, 1, 16, 32));
-    scene2.add(torus);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setClearColor(0xe0e0e0);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const clearPass = new ClearPass();
-
-    const clearMaskPass = new ClearMaskPass();
-
-    const maskPass1 = new MaskPass(scene1, camera);
-    const maskPass2 = new MaskPass(scene2, camera);
-
-    const texture1 = new THREE.TextureLoader().load('textures/758px-Canestra_di_frutta_(Caravaggio).jpg');
-    texture1.colorSpace = THREE.SRGBColorSpace;
-    texture1.minFilter = THREE.LinearFilter;
-    const texture2 = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
-    texture2.colorSpace = THREE.SRGBColorSpace;
-
-    const texturePass1 = new TexturePass(texture1);
-    const texturePass2 = new TexturePass(texture2);
-
-    const outputPass = new OutputPass();
-
-    const parameters = {
-        stencilBuffer: true,
-    };
-
-    const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, parameters);
-
-    composer = new EffectComposer(renderer, renderTarget);
-    composer.addPass(clearPass);
-    composer.addPass(maskPass1);
-    composer.addPass(texturePass1);
-    composer.addPass(clearMaskPass);
-    composer.addPass(maskPass2);
-    composer.addPass(texturePass2);
-    composer.addPass(clearMaskPass);
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    const time = performance.now() * 0.001 + 6000;
-
-    box.position.x = Math.cos(time / 1.5) * 2;
-    box.position.y = Math.sin(time) * 2;
-    box.rotation.x = time;
-    box.rotation.y = time / 2;
-
-    torus.position.x = Math.cos(time) * 2;
-    torus.position.y = Math.sin(time / 1.5) * 2;
-    torus.rotation.x = time;
-    torus.rotation.y = time / 2;
-
-    renderer.clear();
-    composer.render(time);
-}
diff --git a/examples-testing/examples/webgl_postprocessing_material_ao.ts b/examples-testing/examples/webgl_postprocessing_material_ao.ts
deleted file mode 100644
index 2f17a5304..000000000
--- a/examples-testing/examples/webgl_postprocessing_material_ao.ts
+++ /dev/null
@@ -1,277 +0,0 @@
-import * as THREE from 'three';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { MeshPostProcessingMaterial } from 'three/addons/materials/MeshPostProcessingMaterial.js';
-
-let renderer, camera, scene, composer, controls, stats;
-const sceneParameters = {
-    output: 0,
-    envMapIntensity: 1.0,
-    ambientLightIntensity: 0.0,
-    lightIntensity: 50,
-    shadow: true,
-};
-const aoParameters = {
-    radius: 0.5,
-    distanceExponent: 2,
-    thickness: 10,
-    scale: 1,
-    samples: 16,
-    distanceFallOff: 1,
-};
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-    renderer.shadowMap.enabled = sceneParameters.shadow;
-
-    const plyLoader = new PLYLoader();
-    const rgbeloader = new RGBELoader();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 50);
-    camera.position.set(0, 3, 5);
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 1, 0);
-    controls.update();
-    controls.enablePan = false;
-    controls.enableDamping = true;
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    scene = new THREE.Scene();
-    composer = new EffectComposer(renderer);
-
-    const gtaoPass = new GTAOPass(scene, camera, width, height);
-    gtaoPass.output = GTAOPass.OUTPUT.Off;
-    const renderPasse = new RenderPass(scene, camera);
-    const outputPass = new OutputPass();
-
-    composer.addPass(gtaoPass);
-    composer.addPass(renderPasse);
-    composer.addPass(outputPass);
-
-    rgbeloader.load('textures/equirectangular/royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-        scene.environment = texture;
-    });
-
-    const groundMaterial = new MeshPostProcessingMaterial({
-        color: 0x7f7f7f,
-        envMapIntensity: sceneParameters.envMapIntensity,
-        aoPassMap: gtaoPass.gtaoMap,
-    });
-    const objectMaterial = new MeshPostProcessingMaterial({
-        color: 0xffffff,
-        roughness: 0.5,
-        metalness: 0.5,
-        envMapIntensity: sceneParameters.envMapIntensity,
-        aoPassMap: gtaoPass.gtaoMap,
-    });
-    const emissiveMaterial = new MeshPostProcessingMaterial({
-        color: 0,
-        emissive: 0xffffff,
-        aoPassMap: gtaoPass.gtaoMap,
-    });
-    plyLoader.load('models/ply/binary/Lucy100k.ply', geometry => {
-        geometry.computeVertexNormals();
-        const lucy = new THREE.Mesh(geometry, objectMaterial);
-        lucy.receiveShadow = true;
-        lucy.castShadow = true;
-        lucy.scale.setScalar(0.001);
-        lucy.rotation.set(0, Math.PI, 0);
-        lucy.position.set(0.04, 1.8, 0.02);
-        scene.add(lucy);
-    });
-    const ambientLight = new THREE.AmbientLight(0xffffff, sceneParameters.ambientLightIntensity);
-    const lightGroup = new THREE.Group();
-    const planeGeometry = new THREE.PlaneGeometry(6, 6);
-    const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 64);
-    const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
-    const lightSphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
-    scene.background = new THREE.Color(0xbfe3dd);
-    scene.add(ambientLight);
-    scene.add(lightGroup);
-    const targetObject = new THREE.Object3D();
-    targetObject.position.set(0, 1, 0);
-    scene.add(targetObject);
-    const lightColors = [0xff4040, 0x40ff40, 0x4040ff];
-    for (let j = 0; j < 3; ++j) {
-        const light = new THREE.SpotLight(lightColors[j], sceneParameters.lightIntensity, 0, Math.PI / 9);
-        light.castShadow = true;
-        light.shadow.camera.far = 15;
-        light.position.set(5 * Math.cos((Math.PI * j * 2) / 3), 2.5, 5 * Math.sin((Math.PI * j * 2) / 3));
-        light.target = targetObject;
-        lightGroup.add(light);
-    }
-
-    const groundPlane = new THREE.Mesh(planeGeometry, groundMaterial);
-    groundPlane.rotation.x = -Math.PI / 2;
-    groundPlane.position.set(0, 0, 0);
-    groundPlane.receiveShadow = true;
-    scene.add(groundPlane);
-    const pedestal = new THREE.Mesh(cylinderGeometry, groundMaterial);
-    pedestal.position.set(0, 0.5, 0);
-    pedestal.receiveShadow = true;
-    pedestal.castShadow = true;
-    scene.add(pedestal);
-    const sphereMesh = new THREE.InstancedMesh(sphereGeometry, objectMaterial, 6);
-    sphereMesh.receiveShadow = true;
-    sphereMesh.castShadow = true;
-    scene.add(sphereMesh);
-    [...Array(6).keys()].forEach(i =>
-        sphereMesh.setMatrixAt(
-            i,
-            new THREE.Matrix4().makeTranslation(Math.cos((Math.PI * i) / 3), 0.5, Math.sin((Math.PI * i) / 3)),
-        ),
-    );
-    const lightSphereMesh = new THREE.InstancedMesh(lightSphereGeometry, emissiveMaterial, 4);
-    scene.add(lightSphereMesh);
-    [...Array(4).keys()].forEach(i =>
-        lightSphereMesh.setMatrixAt(
-            i,
-            new THREE.Matrix4().makeTranslation(
-                0.4 * Math.cos((Math.PI * (i + 0.5)) / 2),
-                1.1,
-                0.45 * Math.sin((Math.PI * (i + 0.5)) / 2),
-            ),
-        ),
-    );
-
-    const updateGtaoMaterial = () => gtaoPass.updateGtaoMaterial(aoParameters);
-    const updateOutput = () => {
-        composer.removePass(gtaoPass);
-        composer.insertPass(gtaoPass, sceneParameters.output == 1 ? 1 : 0);
-
-        switch (sceneParameters.output) {
-            default:
-            case 0:
-                gtaoPass.output = GTAOPass.OUTPUT.Off;
-                gtaoPass.enabled = true;
-                renderPasse.enabled = true;
-                break;
-            case 1:
-                gtaoPass.output = GTAOPass.OUTPUT.Default;
-                gtaoPass.enabled = true;
-                renderPasse.enabled = true;
-                break;
-            case 2:
-                gtaoPass.output = GTAOPass.OUTPUT.Diffuse;
-                gtaoPass.enabled = false;
-                renderPasse.enabled = true;
-                break;
-            case 3:
-                gtaoPass.output = GTAOPass.OUTPUT.Denoise;
-                gtaoPass.enabled = true;
-                renderPasse.enabled = false;
-                break;
-        }
-
-        groundMaterial.aoPassMap = sceneParameters.output === 0 ? gtaoPass.gtaoMap : null;
-        objectMaterial.aoPassMap = sceneParameters.output === 0 ? gtaoPass.gtaoMap : null;
-    };
-
-    updateOutput();
-    updateGtaoMaterial();
-
-    const gui = new GUI();
-    gui.add(sceneParameters, 'output', {
-        'material AO': 0,
-        'post blended AO': 1,
-        'only diffuse': 2,
-        'only AO': 3,
-    }).onChange(() => updateOutput());
-    gui.add(sceneParameters, 'envMapIntensity')
-        .min(0)
-        .max(1)
-        .step(0.01)
-        .onChange(() => {
-            groundMaterial.envMapIntensity = sceneParameters.envMapIntensity;
-            objectMaterial.envMapIntensity = sceneParameters.envMapIntensity;
-        });
-    gui.add(sceneParameters, 'ambientLightIntensity')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(() => {
-            ambientLight.intensity = sceneParameters.ambientLightIntensity;
-        });
-    gui.add(sceneParameters, 'lightIntensity')
-        .min(0)
-        .max(100)
-        .step(1)
-        .onChange(() => {
-            lightGroup.children.forEach(light => (light.intensity = sceneParameters.lightIntensity));
-        });
-    gui.add(sceneParameters, 'shadow').onChange(value => {
-        renderer.shadowMap.enabled = value;
-        lightGroup.children.forEach(light => (light.castShadow = value));
-    });
-    gui.add(aoParameters, 'radius')
-        .min(0.01)
-        .max(2)
-        .step(0.01)
-        .onChange(() => updateGtaoMaterial());
-    gui.add(aoParameters, 'distanceExponent')
-        .min(1)
-        .max(4)
-        .step(0.01)
-        .onChange(() => updateGtaoMaterial());
-    gui.add(aoParameters, 'thickness')
-        .min(0.01)
-        .max(10)
-        .step(0.01)
-        .onChange(() => updateGtaoMaterial());
-    gui.add(aoParameters, 'distanceFallOff')
-        .min(0)
-        .max(1)
-        .step(0.01)
-        .onChange(() => updateGtaoMaterial());
-    gui.add(aoParameters, 'scale')
-        .min(0.01)
-        .max(2.0)
-        .step(0.01)
-        .onChange(() => updateGtaoMaterial());
-    gui.add(aoParameters, 'samples')
-        .min(2)
-        .max(32)
-        .step(1)
-        .onChange(() => updateGtaoMaterial());
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    controls.update();
-    stats.begin();
-    composer.render();
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_outline.ts b/examples-testing/examples/webgl_postprocessing_outline.ts
deleted file mode 100644
index 356575460..000000000
--- a/examples-testing/examples/webgl_postprocessing_outline.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
-
-let container, stats;
-let camera, scene, renderer, controls;
-let composer, effectFXAA, outlinePass;
-
-let selectedObjects = [];
-
-const raycaster = new THREE.Raycaster();
-const mouse = new THREE.Vector2();
-
-const obj3d = new THREE.Object3D();
-const group = new THREE.Group();
-
-const params = {
-    edgeStrength: 3.0,
-    edgeGlow: 0.0,
-    edgeThickness: 1.0,
-    pulsePeriod: 0,
-    rotate: false,
-    usePatternTexture: false,
-};
-
-// Init gui
-
-const gui = new GUI({ width: 280 });
-
-gui.add(params, 'edgeStrength', 0.01, 10).onChange(function (value) {
-    outlinePass.edgeStrength = Number(value);
-});
-
-gui.add(params, 'edgeGlow', 0.0, 1).onChange(function (value) {
-    outlinePass.edgeGlow = Number(value);
-});
-
-gui.add(params, 'edgeThickness', 1, 4).onChange(function (value) {
-    outlinePass.edgeThickness = Number(value);
-});
-
-gui.add(params, 'pulsePeriod', 0.0, 5).onChange(function (value) {
-    outlinePass.pulsePeriod = Number(value);
-});
-
-gui.add(params, 'rotate');
-
-gui.add(params, 'usePatternTexture').onChange(function (value) {
-    outlinePass.usePatternTexture = value;
-});
-
-function Configuration() {
-    this.visibleEdgeColor = '#ffffff';
-    this.hiddenEdgeColor = '#190a05';
-}
-
-const conf = new Configuration();
-
-gui.addColor(conf, 'visibleEdgeColor').onChange(function (value) {
-    outlinePass.visibleEdgeColor.set(value);
-});
-
-gui.addColor(conf, 'hiddenEdgeColor').onChange(function (value) {
-    outlinePass.hiddenEdgeColor.set(value);
-});
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.shadowMap.enabled = true;
-    // todo - support pixelRatio in this demo
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100);
-    camera.position.set(0, 0, 8);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 5;
-    controls.maxDistance = 20;
-    controls.enablePan = false;
-    controls.enableDamping = true;
-    controls.dampingFactor = 0.05;
-
-    //
-
-    scene.add(new THREE.AmbientLight(0xaaaaaa, 0.6));
-
-    const light = new THREE.DirectionalLight(0xddffdd, 2);
-    light.position.set(1, 1, 1);
-    light.castShadow = true;
-    light.shadow.mapSize.width = 1024;
-    light.shadow.mapSize.height = 1024;
-
-    const d = 10;
-
-    light.shadow.camera.left = -d;
-    light.shadow.camera.right = d;
-    light.shadow.camera.top = d;
-    light.shadow.camera.bottom = -d;
-    light.shadow.camera.far = 1000;
-
-    scene.add(light);
-
-    // model
-
-    const loader = new OBJLoader();
-    loader.load('models/obj/tree.obj', function (object) {
-        let scale = 1.0;
-
-        object.traverse(function (child) {
-            if (child instanceof THREE.Mesh) {
-                child.geometry.center();
-                child.geometry.computeBoundingSphere();
-                scale = 0.2 * child.geometry.boundingSphere.radius;
-
-                const phongMaterial = new THREE.MeshPhongMaterial({
-                    color: 0xffffff,
-                    specular: 0x111111,
-                    shininess: 5,
-                });
-                child.material = phongMaterial;
-                child.receiveShadow = true;
-                child.castShadow = true;
-            }
-        });
-
-        object.position.y = 1;
-        object.scale.divideScalar(scale);
-        obj3d.add(object);
-    });
-
-    scene.add(group);
-
-    group.add(obj3d);
-
-    //
-
-    const geometry = new THREE.SphereGeometry(3, 48, 24);
-
-    for (let i = 0; i < 20; i++) {
-        const material = new THREE.MeshLambertMaterial();
-        material.color.setHSL(Math.random(), 1.0, 0.3);
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 4 - 2;
-        mesh.position.y = Math.random() * 4 - 2;
-        mesh.position.z = Math.random() * 4 - 2;
-        mesh.receiveShadow = true;
-        mesh.castShadow = true;
-        mesh.scale.multiplyScalar(Math.random() * 0.3 + 0.1);
-        group.add(mesh);
-    }
-
-    const floorMaterial = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide });
-
-    const floorGeometry = new THREE.PlaneGeometry(12, 12);
-    const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
-    floorMesh.rotation.x -= Math.PI * 0.5;
-    floorMesh.position.y -= 1.5;
-    group.add(floorMesh);
-    floorMesh.receiveShadow = true;
-
-    const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);
-    const torusMaterial = new THREE.MeshPhongMaterial({ color: 0xffaaff });
-    const torus = new THREE.Mesh(torusGeometry, torusMaterial);
-    torus.position.z = -4;
-    group.add(torus);
-    torus.receiveShadow = true;
-    torus.castShadow = true;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-
-    const renderPass = new RenderPass(scene, camera);
-    composer.addPass(renderPass);
-
-    outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
-    composer.addPass(outlinePass);
-
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/tri_pattern.jpg', function (texture) {
-        outlinePass.patternTexture = texture;
-        texture.wrapS = THREE.RepeatWrapping;
-        texture.wrapT = THREE.RepeatWrapping;
-    });
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    effectFXAA = new ShaderPass(FXAAShader);
-    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
-    composer.addPass(effectFXAA);
-
-    window.addEventListener('resize', onWindowResize);
-
-    renderer.domElement.style.touchAction = 'none';
-    renderer.domElement.addEventListener('pointermove', onPointerMove);
-
-    function onPointerMove(event) {
-        if (event.isPrimary === false) return;
-
-        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-        checkIntersection();
-    }
-
-    function addSelectedObject(object) {
-        selectedObjects = [];
-        selectedObjects.push(object);
-    }
-
-    function checkIntersection() {
-        raycaster.setFromCamera(mouse, camera);
-
-        const intersects = raycaster.intersectObject(scene, true);
-
-        if (intersects.length > 0) {
-            const selectedObject = intersects[0].object;
-            addSelectedObject(selectedObject);
-            outlinePass.selectedObjects = selectedObjects;
-        } else {
-            // outlinePass.selectedObjects = [];
-        }
-    }
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-
-    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
-}
-
-function animate() {
-    stats.begin();
-
-    const timer = performance.now();
-
-    if (params.rotate) {
-        group.rotation.y = timer * 0.0001;
-    }
-
-    controls.update();
-
-    composer.render();
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_pixel.ts b/examples-testing/examples/webgl_postprocessing_pixel.ts
deleted file mode 100644
index 15b54d072..000000000
--- a/examples-testing/examples/webgl_postprocessing_pixel.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPixelatedPass } from 'three/addons/postprocessing/RenderPixelatedPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, composer, crystalMesh, clock;
-let gui, params;
-
-init();
-
-function init() {
-    const aspectRatio = window.innerWidth / window.innerHeight;
-
-    camera = new THREE.OrthographicCamera(-aspectRatio, aspectRatio, 1, -1, 0.1, 10);
-    camera.position.y = 2 * Math.tan(Math.PI / 6);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x151729);
-
-    clock = new THREE.Clock();
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.shadowMap.enabled = true;
-    //renderer.setPixelRatio( window.devicePixelRatio );
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    composer = new EffectComposer(renderer);
-    const renderPixelatedPass = new RenderPixelatedPass(6, scene, camera);
-    composer.addPass(renderPixelatedPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxZoom = 2;
-
-    // gui
-
-    gui = new GUI();
-    params = { pixelSize: 6, normalEdgeStrength: 0.3, depthEdgeStrength: 0.4, pixelAlignedPanning: true };
-    gui.add(params, 'pixelSize')
-        .min(1)
-        .max(16)
-        .step(1)
-        .onChange(() => {
-            renderPixelatedPass.setPixelSize(params.pixelSize);
-        });
-    gui.add(renderPixelatedPass, 'normalEdgeStrength').min(0).max(2).step(0.05);
-    gui.add(renderPixelatedPass, 'depthEdgeStrength').min(0).max(1).step(0.05);
-    gui.add(params, 'pixelAlignedPanning');
-
-    // textures
-
-    const loader = new THREE.TextureLoader();
-    const texChecker = pixelTexture(loader.load('textures/checker.png'));
-    const texChecker2 = pixelTexture(loader.load('textures/checker.png'));
-    texChecker.repeat.set(3, 3);
-    texChecker2.repeat.set(1.5, 1.5);
-
-    // meshes
-
-    const boxMaterial = new THREE.MeshPhongMaterial({ map: texChecker2 });
-
-    function addBox(boxSideLength, x, z, rotation) {
-        const mesh = new THREE.Mesh(new THREE.BoxGeometry(boxSideLength, boxSideLength, boxSideLength), boxMaterial);
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-        mesh.rotation.y = rotation;
-        mesh.position.y = boxSideLength / 2;
-        mesh.position.set(x, boxSideLength / 2 + 0.0001, z);
-        scene.add(mesh);
-        return mesh;
-    }
-
-    addBox(0.4, 0, 0, Math.PI / 4);
-    addBox(0.5, -0.5, -0.5, Math.PI / 4);
-
-    const planeSideLength = 2;
-    const planeMesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(planeSideLength, planeSideLength),
-        new THREE.MeshPhongMaterial({ map: texChecker }),
-    );
-    planeMesh.receiveShadow = true;
-    planeMesh.rotation.x = -Math.PI / 2;
-    scene.add(planeMesh);
-
-    const radius = 0.2;
-    const geometry = new THREE.IcosahedronGeometry(radius);
-    crystalMesh = new THREE.Mesh(
-        geometry,
-        new THREE.MeshPhongMaterial({
-            color: 0x68b7e9,
-            emissive: 0x4f7e8b,
-            shininess: 10,
-            specular: 0xffffff,
-        }),
-    );
-    crystalMesh.receiveShadow = true;
-    crystalMesh.castShadow = true;
-    scene.add(crystalMesh);
-
-    // lights
-
-    scene.add(new THREE.AmbientLight(0x757f8e, 3));
-
-    const directionalLight = new THREE.DirectionalLight(0xfffecd, 1.5);
-    directionalLight.position.set(100, 100, 100);
-    directionalLight.castShadow = true;
-    directionalLight.shadow.mapSize.set(2048, 2048);
-    scene.add(directionalLight);
-
-    const spotLight = new THREE.SpotLight(0xffc100, 10, 10, Math.PI / 16, 0.02, 2);
-    spotLight.position.set(2, 2, 0);
-    const target = spotLight.target;
-    scene.add(target);
-    target.position.set(0, 0, 0);
-    spotLight.castShadow = true;
-    scene.add(spotLight);
-}
-
-function onWindowResize() {
-    const aspectRatio = window.innerWidth / window.innerHeight;
-    camera.left = -aspectRatio;
-    camera.right = aspectRatio;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const t = clock.getElapsedTime();
-
-    crystalMesh.material.emissiveIntensity = Math.sin(t * 3) * 0.5 + 0.5;
-    crystalMesh.position.y = 0.7 + Math.sin(t * 2) * 0.05;
-    crystalMesh.rotation.y = stopGoEased(t, 2, 4) * 2 * Math.PI;
-
-    const rendererSize = renderer.getSize(new THREE.Vector2());
-    const aspectRatio = rendererSize.x / rendererSize.y;
-    if (params['pixelAlignedPanning']) {
-        pixelAlignFrustum(
-            camera,
-            aspectRatio,
-            Math.floor(rendererSize.x / params['pixelSize']),
-            Math.floor(rendererSize.y / params['pixelSize']),
-        );
-    } else if (camera.left != -aspectRatio || camera.top != 1.0) {
-        // Reset the Camera Frustum if it has been modified
-        camera.left = -aspectRatio;
-        camera.right = aspectRatio;
-        camera.top = 1.0;
-        camera.bottom = -1.0;
-        camera.updateProjectionMatrix();
-    }
-
-    composer.render();
-}
-
-// Helper functions
-
-function pixelTexture(texture) {
-    texture.minFilter = THREE.NearestFilter;
-    texture.magFilter = THREE.NearestFilter;
-    texture.generateMipmaps = false;
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.wrapT = THREE.RepeatWrapping;
-    texture.colorSpace = THREE.SRGBColorSpace;
-    return texture;
-}
-
-function easeInOutCubic(x) {
-    return x ** 2 * 3 - x ** 3 * 2;
-}
-
-function linearStep(x, edge0, edge1) {
-    const w = edge1 - edge0;
-    const m = 1 / w;
-    const y0 = -m * edge0;
-    return THREE.MathUtils.clamp(y0 + m * x, 0, 1);
-}
-
-function stopGoEased(x, downtime, period) {
-    const cycle = (x / period) | 0;
-    const tween = x - cycle * period;
-    const linStep = easeInOutCubic(linearStep(tween, downtime, period));
-    return cycle + linStep;
-}
-
-function pixelAlignFrustum(camera, aspectRatio, pixelsPerScreenWidth, pixelsPerScreenHeight) {
-    // 0. Get Pixel Grid Units
-    const worldScreenWidth = (camera.right - camera.left) / camera.zoom;
-    const worldScreenHeight = (camera.top - camera.bottom) / camera.zoom;
-    const pixelWidth = worldScreenWidth / pixelsPerScreenWidth;
-    const pixelHeight = worldScreenHeight / pixelsPerScreenHeight;
-
-    // 1. Project the current camera position along its local rotation bases
-    const camPos = new THREE.Vector3();
-    camera.getWorldPosition(camPos);
-    const camRot = new THREE.Quaternion();
-    camera.getWorldQuaternion(camRot);
-    const camRight = new THREE.Vector3(1.0, 0.0, 0.0).applyQuaternion(camRot);
-    const camUp = new THREE.Vector3(0.0, 1.0, 0.0).applyQuaternion(camRot);
-    const camPosRight = camPos.dot(camRight);
-    const camPosUp = camPos.dot(camUp);
-
-    // 2. Find how far along its position is along these bases in pixel units
-    const camPosRightPx = camPosRight / pixelWidth;
-    const camPosUpPx = camPosUp / pixelHeight;
-
-    // 3. Find the fractional pixel units and convert to world units
-    const fractX = camPosRightPx - Math.round(camPosRightPx);
-    const fractY = camPosUpPx - Math.round(camPosUpPx);
-
-    // 4. Add fractional world units to the left/right top/bottom to align with the pixel grid
-    camera.left = -aspectRatio - fractX * pixelWidth;
-    camera.right = aspectRatio - fractX * pixelWidth;
-    camera.top = 1.0 - fractY * pixelHeight;
-    camera.bottom = -1.0 - fractY * pixelHeight;
-    camera.updateProjectionMatrix();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_procedural.ts b/examples-testing/examples/webgl_postprocessing_procedural.ts
deleted file mode 100644
index 869824270..000000000
--- a/examples-testing/examples/webgl_postprocessing_procedural.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let postCamera, postScene, renderer;
-let postMaterial, noiseRandom1DMaterial, noiseRandom2DMaterial, noiseRandom3DMaterial, postQuad;
-let stats;
-
-const params = { procedure: 'noiseRandom3D' };
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // Setup post processing stage
-    postCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
-    noiseRandom1DMaterial = new THREE.ShaderMaterial({
-        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
-        fragmentShader: document.querySelector('#noiseRandom1D-frag').textContent.trim(),
-    });
-    noiseRandom2DMaterial = new THREE.ShaderMaterial({
-        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
-        fragmentShader: document.querySelector('#noiseRandom2D-frag').textContent.trim(),
-    });
-    noiseRandom3DMaterial = new THREE.ShaderMaterial({
-        vertexShader: document.querySelector('#procedural-vert').textContent.trim(),
-        fragmentShader: document.querySelector('#noiseRandom3D-frag').textContent.trim(),
-    });
-    postMaterial = noiseRandom3DMaterial;
-    const postPlane = new THREE.PlaneGeometry(2, 2);
-    postQuad = new THREE.Mesh(postPlane, postMaterial);
-    postScene = new THREE.Scene();
-    postScene.add(postQuad);
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'procedure', ['noiseRandom1D', 'noiseRandom2D', 'noiseRandom3D']);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    switch (params.procedure) {
-        case 'noiseRandom1D':
-            postMaterial = noiseRandom1DMaterial;
-            break;
-        case 'noiseRandom2D':
-            postMaterial = noiseRandom2DMaterial;
-            break;
-        case 'noiseRandom3D':
-            postMaterial = noiseRandom3DMaterial;
-            break;
-    }
-
-    postQuad.material = postMaterial;
-
-    // render post FX
-    renderer.render(postScene, postCamera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts b/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
deleted file mode 100644
index fa46d4c8d..000000000
--- a/examples-testing/examples/webgl_postprocessing_rgb_halftone.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { HalftonePass } from 'three/addons/postprocessing/HalftonePass.js';
-
-let renderer, clock, camera, stats;
-
-const rotationSpeed = Math.PI / 64;
-
-let composer, group;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-
-    clock = new THREE.Clock();
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 12;
-
-    stats = new Stats();
-
-    document.body.appendChild(renderer.domElement);
-    document.body.appendChild(stats.dom);
-
-    // camera controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0, 0);
-    controls.update();
-
-    // scene
-
-    const scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x444444);
-
-    group = new THREE.Group();
-    const floor = new THREE.Mesh(new THREE.BoxGeometry(100, 1, 100), new THREE.MeshPhongMaterial({}));
-    floor.position.y = -10;
-    const light = new THREE.PointLight(0xffffff, 250);
-    light.position.y = 2;
-    group.add(floor, light);
-    scene.add(group);
-
-    const mat = new THREE.ShaderMaterial({
-        uniforms: {},
-
-        vertexShader: [
-            'varying vec2 vUV;',
-            'varying vec3 vNormal;',
-
-            'void main() {',
-
-            'vUV = uv;',
-            'vNormal = vec3( normal );',
-            'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
-
-            '}',
-        ].join('\n'),
-
-        fragmentShader: [
-            'varying vec2 vUV;',
-            'varying vec3 vNormal;',
-
-            'void main() {',
-
-            'vec4 c = vec4( abs( vNormal ) + vec3( vUV, 0.0 ), 0.0 );',
-            'gl_FragColor = c;',
-
-            '}',
-        ].join('\n'),
-    });
-
-    for (let i = 0; i < 50; ++i) {
-        // fill scene with coloured cubes
-        const mesh = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), mat);
-        mesh.position.set(Math.random() * 16 - 8, Math.random() * 16 - 8, Math.random() * 16 - 8);
-        mesh.rotation.set(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
-        group.add(mesh);
-    }
-
-    // post-processing
-
-    composer = new EffectComposer(renderer);
-    const renderPass = new RenderPass(scene, camera);
-    const params = {
-        shape: 1,
-        radius: 4,
-        rotateR: Math.PI / 12,
-        rotateB: (Math.PI / 12) * 2,
-        rotateG: (Math.PI / 12) * 3,
-        scatter: 0,
-        blending: 1,
-        blendingMode: 1,
-        greyscale: false,
-        disable: false,
-    };
-    const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, params);
-    composer.addPass(renderPass);
-    composer.addPass(halftonePass);
-
-    window.onresize = function () {
-        // resize composer
-        renderer.setSize(window.innerWidth, window.innerHeight);
-        composer.setSize(window.innerWidth, window.innerHeight);
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-    };
-
-    // GUI
-
-    const controller = {
-        radius: halftonePass.uniforms['radius'].value,
-        rotateR: halftonePass.uniforms['rotateR'].value / (Math.PI / 180),
-        rotateG: halftonePass.uniforms['rotateG'].value / (Math.PI / 180),
-        rotateB: halftonePass.uniforms['rotateB'].value / (Math.PI / 180),
-        scatter: halftonePass.uniforms['scatter'].value,
-        shape: halftonePass.uniforms['shape'].value,
-        greyscale: halftonePass.uniforms['greyscale'].value,
-        blending: halftonePass.uniforms['blending'].value,
-        blendingMode: halftonePass.uniforms['blendingMode'].value,
-        disable: halftonePass.uniforms['disable'].value,
-    };
-
-    function onGUIChange() {
-        // update uniforms
-        halftonePass.uniforms['radius'].value = controller.radius;
-        halftonePass.uniforms['rotateR'].value = controller.rotateR * (Math.PI / 180);
-        halftonePass.uniforms['rotateG'].value = controller.rotateG * (Math.PI / 180);
-        halftonePass.uniforms['rotateB'].value = controller.rotateB * (Math.PI / 180);
-        halftonePass.uniforms['scatter'].value = controller.scatter;
-        halftonePass.uniforms['shape'].value = controller.shape;
-        halftonePass.uniforms['greyscale'].value = controller.greyscale;
-        halftonePass.uniforms['blending'].value = controller.blending;
-        halftonePass.uniforms['blendingMode'].value = controller.blendingMode;
-        halftonePass.uniforms['disable'].value = controller.disable;
-    }
-
-    const gui = new GUI();
-    gui.add(controller, 'shape', { Dot: 1, Ellipse: 2, Line: 3, Square: 4 }).onChange(onGUIChange);
-    gui.add(controller, 'radius', 1, 25).onChange(onGUIChange);
-    gui.add(controller, 'rotateR', 0, 90).onChange(onGUIChange);
-    gui.add(controller, 'rotateG', 0, 90).onChange(onGUIChange);
-    gui.add(controller, 'rotateB', 0, 90).onChange(onGUIChange);
-    gui.add(controller, 'scatter', 0, 1, 0.01).onChange(onGUIChange);
-    gui.add(controller, 'greyscale').onChange(onGUIChange);
-    gui.add(controller, 'blending', 0, 1, 0.01).onChange(onGUIChange);
-    gui.add(controller, 'blendingMode', { Linear: 1, Multiply: 2, Add: 3, Lighter: 4, Darker: 5 }).onChange(
-        onGUIChange,
-    );
-    gui.add(controller, 'disable').onChange(onGUIChange);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-    stats.update();
-    group.rotation.y += delta * rotationSpeed;
-    composer.render(delta);
-}
diff --git a/examples-testing/examples/webgl_postprocessing_sao.ts b/examples-testing/examples/webgl_postprocessing_sao.ts
deleted file mode 100644
index bf40d026b..000000000
--- a/examples-testing/examples/webgl_postprocessing_sao.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { SAOPass } from 'three/addons/postprocessing/SAOPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let container, stats;
-let camera, scene, renderer;
-let composer, renderPass, saoPass;
-let group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(65, width / height, 3, 10);
-    camera.position.z = 7;
-
-    scene = new THREE.Scene();
-
-    group = new THREE.Object3D();
-    scene.add(group);
-
-    const light = new THREE.PointLight(0xefffef, 500);
-    light.position.z = 10;
-    light.position.y = -10;
-    light.position.x = -10;
-    scene.add(light);
-
-    const light2 = new THREE.PointLight(0xffefef, 500);
-    light2.position.z = 10;
-    light2.position.x = -10;
-    light2.position.y = 10;
-    scene.add(light2);
-
-    const light3 = new THREE.PointLight(0xefefff, 500);
-    light3.position.z = 10;
-    light3.position.x = 10;
-    light3.position.y = -10;
-    scene.add(light3);
-
-    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
-    scene.add(light4);
-
-    const geometry = new THREE.SphereGeometry(3, 48, 24);
-
-    for (let i = 0; i < 120; i++) {
-        const material = new THREE.MeshStandardMaterial();
-        material.roughness = 0.5 * Math.random() + 0.25;
-        material.metalness = 0;
-        material.color.setHSL(Math.random(), 1.0, 0.3);
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 4 - 2;
-        mesh.position.y = Math.random() * 4 - 2;
-        mesh.position.z = Math.random() * 4 - 2;
-        mesh.rotation.x = Math.random();
-        mesh.rotation.y = Math.random();
-        mesh.rotation.z = Math.random();
-
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 0.2 + 0.05;
-        group.add(mesh);
-    }
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    composer = new EffectComposer(renderer);
-    renderPass = new RenderPass(scene, camera);
-    composer.addPass(renderPass);
-    saoPass = new SAOPass(scene, camera);
-    composer.addPass(saoPass);
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    // Init gui
-    const gui = new GUI();
-    gui.add(saoPass.params, 'output', {
-        Default: SAOPass.OUTPUT.Default,
-        'SAO Only': SAOPass.OUTPUT.SAO,
-        Normal: SAOPass.OUTPUT.Normal,
-    }).onChange(function (value) {
-        saoPass.params.output = value;
-    });
-    gui.add(saoPass.params, 'saoBias', -1, 1);
-    gui.add(saoPass.params, 'saoIntensity', 0, 1);
-    gui.add(saoPass.params, 'saoScale', 0, 10);
-    gui.add(saoPass.params, 'saoKernelRadius', 1, 100);
-    gui.add(saoPass.params, 'saoMinResolution', 0, 1);
-    gui.add(saoPass.params, 'saoBlur');
-    gui.add(saoPass.params, 'saoBlurRadius', 0, 200);
-    gui.add(saoPass.params, 'saoBlurStdDev', 0.5, 150);
-    gui.add(saoPass.params, 'saoBlurDepthCutoff', 0.0, 0.1);
-    gui.add(saoPass, 'enabled');
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth || 1;
-    const height = window.innerHeight || 1;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-    renderer.setSize(width, height);
-
-    composer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    const timer = performance.now();
-    group.rotation.x = timer * 0.0002;
-    group.rotation.y = timer * 0.0001;
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_smaa.ts b/examples-testing/examples/webgl_postprocessing_smaa.ts
deleted file mode 100644
index 6f71f6478..000000000
--- a/examples-testing/examples/webgl_postprocessing_smaa.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, composer, stats, smaaPass;
-
-const params = {
-    enabled: true,
-    autoRotate: true,
-};
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.BoxGeometry(120, 120, 120);
-    const material1 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
-
-    const mesh1 = new THREE.Mesh(geometry, material1);
-    mesh1.position.x = -100;
-    scene.add(mesh1);
-
-    const texture = new THREE.TextureLoader().load('textures/brick_diffuse.jpg');
-    texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const material2 = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh2 = new THREE.Mesh(geometry, material2);
-    mesh2.position.x = 100;
-    scene.add(mesh2);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    composer.addPass(new RenderPass(scene, camera));
-
-    smaaPass = new SMAAPass(
-        window.innerWidth * renderer.getPixelRatio(),
-        window.innerHeight * renderer.getPixelRatio(),
-    );
-    composer.addPass(smaaPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-
-    const gui = new GUI();
-
-    const smaaFolder = gui.addFolder('SMAA');
-    smaaFolder.add(params, 'enabled');
-
-    const sceneFolder = gui.addFolder('Scene');
-    sceneFolder.add(params, 'autoRotate');
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-
-    if (params.autoRotate === true) {
-        for (let i = 0; i < scene.children.length; i++) {
-            const child = scene.children[i];
-
-            child.rotation.x += 0.005;
-            child.rotation.y += 0.01;
-        }
-    }
-
-    smaaPass.enabled = params.enabled;
-
-    composer.render();
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_sobel.ts b/examples-testing/examples/webgl_postprocessing_sobel.ts
deleted file mode 100644
index 55d88dc02..000000000
--- a/examples-testing/examples/webgl_postprocessing_sobel.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-
-import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
-import { SobelOperatorShader } from 'three/addons/shaders/SobelOperatorShader.js';
-
-let camera, scene, renderer, composer;
-
-let effectSobel;
-
-const params = {
-    enable: true,
-};
-
-init();
-
-function init() {
-    //
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 1, 3);
-    camera.lookAt(scene.position);
-
-    //
-
-    const geometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 32);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffff00 });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xe7e7e7);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 20);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    const renderPass = new RenderPass(scene, camera);
-    composer.addPass(renderPass);
-
-    // color to grayscale conversion
-
-    const effectGrayScale = new ShaderPass(LuminosityShader);
-    composer.addPass(effectGrayScale);
-
-    // you might want to use a gaussian blur filter before
-    // the next pass to improve the result of the Sobel operator
-
-    // Sobel operator
-
-    effectSobel = new ShaderPass(SobelOperatorShader);
-    effectSobel.uniforms['resolution'].value.x = window.innerWidth * window.devicePixelRatio;
-    effectSobel.uniforms['resolution'].value.y = window.innerHeight * window.devicePixelRatio;
-    composer.addPass(effectSobel);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'enable');
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-
-    effectSobel.uniforms['resolution'].value.x = window.innerWidth * window.devicePixelRatio;
-    effectSobel.uniforms['resolution'].value.y = window.innerHeight * window.devicePixelRatio;
-}
-
-function animate() {
-    if (params.enable === true) {
-        composer.render();
-    } else {
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgl_postprocessing_ssaa.ts b/examples-testing/examples/webgl_postprocessing_ssaa.ts
deleted file mode 100644
index 429e02dee..000000000
--- a/examples-testing/examples/webgl_postprocessing_ssaa.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { SSAARenderPass } from 'three/addons/postprocessing/SSAARenderPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let scene, renderer, composer;
-let cameraP, ssaaRenderPassP;
-let cameraO, ssaaRenderPassO;
-let gui, stats;
-
-const params = {
-    sampleLevel: 4,
-    unbiased: true,
-    camera: 'perspective',
-    clearColor: 'black',
-    clearAlpha: 1.0,
-    viewOffsetX: 0,
-    autoRotate: true,
-};
-
-init();
-
-clearGui();
-
-function clearGui() {
-    if (gui) gui.destroy();
-
-    gui = new GUI();
-
-    gui.add(params, 'unbiased');
-    gui.add(params, 'sampleLevel', {
-        'Level 0: 1 Sample': 0,
-        'Level 1: 2 Samples': 1,
-        'Level 2: 4 Samples': 2,
-        'Level 3: 8 Samples': 3,
-        'Level 4: 16 Samples': 4,
-        'Level 5: 32 Samples': 5,
-    });
-    gui.add(params, 'camera', ['perspective', 'orthographic']);
-    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
-    gui.add(params, 'clearAlpha', 0, 1);
-    gui.add(params, 'viewOffsetX', -100, 100);
-    gui.add(params, 'autoRotate');
-
-    gui.open();
-}
-
-function init() {
-    const container = document.getElementById('container');
-
-    const width = window.innerWidth || 1;
-    const height = window.innerHeight || 1;
-    const aspect = width / height;
-    const devicePixelRatio = window.devicePixelRatio || 1;
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    cameraP = new THREE.PerspectiveCamera(65, aspect, 3, 10);
-    cameraP.position.z = 7;
-    cameraP.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
-
-    cameraO = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 3, 10);
-    cameraO.position.z = 7;
-
-    const fov = THREE.MathUtils.degToRad(cameraP.fov);
-    const hyperfocus = (cameraP.near + cameraP.far) / 2;
-    const _height = 2 * Math.tan(fov / 2) * hyperfocus;
-    cameraO.zoom = height / _height;
-
-    scene = new THREE.Scene();
-
-    const group = new THREE.Group();
-    scene.add(group);
-
-    const light = new THREE.PointLight(0xefffef, 500);
-    light.position.z = 10;
-    light.position.y = -10;
-    light.position.x = -10;
-    scene.add(light);
-
-    const light2 = new THREE.PointLight(0xffefef, 500);
-    light2.position.z = 10;
-    light2.position.x = -10;
-    light2.position.y = 10;
-    scene.add(light2);
-
-    const light3 = new THREE.PointLight(0xefefff, 500);
-    light3.position.z = 10;
-    light3.position.x = 10;
-    light3.position.y = -10;
-    scene.add(light3);
-
-    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
-    scene.add(light4);
-
-    const geometry = new THREE.SphereGeometry(3, 48, 24);
-
-    for (let i = 0; i < 120; i++) {
-        const material = new THREE.MeshStandardMaterial();
-        material.roughness = 0.5 * Math.random() + 0.25;
-        material.metalness = 0;
-        material.color.setHSL(Math.random(), 1.0, 0.3);
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 4 - 2;
-        mesh.position.y = Math.random() * 4 - 2;
-        mesh.position.z = Math.random() * 4 - 2;
-        mesh.rotation.x = Math.random();
-        mesh.rotation.y = Math.random();
-        mesh.rotation.z = Math.random();
-
-        mesh.scale.setScalar(Math.random() * 0.2 + 0.05);
-        group.add(mesh);
-    }
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-    composer.setPixelRatio(1); // ensure pixel ratio is always 1 for performance reasons
-    ssaaRenderPassP = new SSAARenderPass(scene, cameraP);
-    composer.addPass(ssaaRenderPassP);
-    ssaaRenderPassO = new SSAARenderPass(scene, cameraO);
-    composer.addPass(ssaaRenderPassO);
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-    const aspect = width / height;
-
-    cameraP.aspect = aspect;
-    cameraP.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
-    cameraO.updateProjectionMatrix();
-
-    cameraO.left = -height * aspect;
-    cameraO.right = height * aspect;
-    cameraO.top = height;
-    cameraO.bottom = -height;
-    cameraO.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-
-    if (params.autoRotate) {
-        for (let i = 0; i < scene.children.length; i++) {
-            const child = scene.children[i];
-
-            child.rotation.x += 0.005;
-            child.rotation.y += 0.01;
-        }
-    }
-
-    let newColor = ssaaRenderPassP.clearColor;
-
-    switch (params.clearColor) {
-        case 'blue':
-            newColor = 0x0000ff;
-            break;
-        case 'red':
-            newColor = 0xff0000;
-            break;
-        case 'green':
-            newColor = 0x00ff00;
-            break;
-        case 'white':
-            newColor = 0xffffff;
-            break;
-        case 'black':
-            newColor = 0x000000;
-            break;
-    }
-
-    ssaaRenderPassP.clearColor = ssaaRenderPassO.clearColor = newColor;
-    ssaaRenderPassP.clearAlpha = ssaaRenderPassO.clearAlpha = params.clearAlpha;
-
-    ssaaRenderPassP.sampleLevel = ssaaRenderPassO.sampleLevel = params.sampleLevel;
-    ssaaRenderPassP.unbiased = ssaaRenderPassO.unbiased = params.unbiased;
-
-    ssaaRenderPassP.enabled = params.camera === 'perspective';
-    ssaaRenderPassO.enabled = params.camera === 'orthographic';
-
-    cameraP.view.offsetX = params.viewOffsetX;
-
-    composer.render();
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_ssao.ts b/examples-testing/examples/webgl_postprocessing_ssao.ts
deleted file mode 100644
index e55ab0446..000000000
--- a/examples-testing/examples/webgl_postprocessing_ssao.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let container, stats;
-let camera, scene, renderer;
-let composer;
-let group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 100, 700);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xaaaaaa);
-
-    scene.add(new THREE.DirectionalLight(0xffffff, 4));
-    scene.add(new THREE.AmbientLight(0xffffff));
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const geometry = new THREE.BoxGeometry(10, 10, 10);
-
-    for (let i = 0; i < 100; i++) {
-        const material = new THREE.MeshLambertMaterial({
-            color: Math.random() * 0xffffff,
-        });
-
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.x = Math.random() * 400 - 200;
-        mesh.position.y = Math.random() * 400 - 200;
-        mesh.position.z = Math.random() * 400 - 200;
-        mesh.rotation.x = Math.random();
-        mesh.rotation.y = Math.random();
-        mesh.rotation.z = Math.random();
-
-        mesh.scale.setScalar(Math.random() * 10 + 2);
-        group.add(mesh);
-    }
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    composer = new EffectComposer(renderer);
-
-    const renderPass = new RenderPass(scene, camera);
-    composer.addPass(renderPass);
-
-    const ssaoPass = new SSAOPass(scene, camera, width, height);
-    composer.addPass(ssaoPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    // Init gui
-    const gui = new GUI();
-
-    gui.add(ssaoPass, 'output', {
-        Default: SSAOPass.OUTPUT.Default,
-        'SSAO Only': SSAOPass.OUTPUT.SSAO,
-        'SSAO Only + Blur': SSAOPass.OUTPUT.Blur,
-        Depth: SSAOPass.OUTPUT.Depth,
-        Normal: SSAOPass.OUTPUT.Normal,
-    }).onChange(function (value) {
-        ssaoPass.output = value;
-    });
-    gui.add(ssaoPass, 'kernelRadius').min(0).max(32);
-    gui.add(ssaoPass, 'minDistance').min(0.001).max(0.02);
-    gui.add(ssaoPass, 'maxDistance').min(0.01).max(0.3);
-    gui.add(ssaoPass, 'enabled');
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    const timer = performance.now();
-    group.rotation.x = timer * 0.0002;
-    group.rotation.y = timer * 0.0001;
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_ssr.ts b/examples-testing/examples/webgl_postprocessing_ssr.ts
deleted file mode 100644
index 307cfd1de..000000000
--- a/examples-testing/examples/webgl_postprocessing_ssr.ts
+++ /dev/null
@@ -1,261 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { SSRPass } from 'three/addons/postprocessing/SSRPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-import { ReflectorForSSRPass } from 'three/addons/objects/ReflectorForSSRPass.js';
-
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-const params = {
-    enableSSR: true,
-    autoRotate: true,
-    otherMeshes: true,
-    groundReflector: true,
-};
-let composer;
-let ssrPass;
-let gui;
-let stats;
-let controls;
-let camera, scene, renderer;
-const otherMeshes = [];
-let groundReflector;
-const selects = [];
-
-const container = document.querySelector('#container');
-
-// Configure and create Draco decoder.
-const dracoLoader = new DRACOLoader();
-dracoLoader.setDecoderPath('jsm/libs/draco/');
-dracoLoader.setDecoderConfig({ type: 'js' });
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);
-    camera.position.set(0.13271600513224902, 0.3489546826045913, 0.43921296427927076);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x443333);
-    scene.fog = new THREE.Fog(0x443333, 1, 4);
-
-    // Ground
-    const plane = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), new THREE.MeshPhongMaterial({ color: 0xcbcbcb }));
-    plane.rotation.x = -Math.PI / 2;
-    plane.position.y = -0.0001;
-    // plane.receiveShadow = true;
-    scene.add(plane);
-
-    // Lights
-    const hemiLight = new THREE.HemisphereLight(0x8d7c7c, 0x494966, 3);
-    scene.add(hemiLight);
-
-    const spotLight = new THREE.SpotLight();
-    spotLight.intensity = 8;
-    spotLight.angle = Math.PI / 16;
-    spotLight.penumbra = 0.5;
-    // spotLight.castShadow = true;
-    spotLight.position.set(-1, 1, 1);
-    scene.add(spotLight);
-
-    dracoLoader.load('models/draco/bunny.drc', function (geometry) {
-        geometry.computeVertexNormals();
-
-        const material = new THREE.MeshStandardMaterial({ color: 0xa5a5a5 });
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.y = -0.0365;
-        scene.add(mesh);
-        selects.push(mesh);
-
-        // Release decoder resources.
-        dracoLoader.dispose();
-    });
-
-    let geometry, material, mesh;
-
-    geometry = new THREE.BoxGeometry(0.05, 0.05, 0.05);
-    material = new THREE.MeshStandardMaterial({ color: 'green' });
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.position.set(-0.12, 0.025, 0.015);
-    scene.add(mesh);
-    otherMeshes.push(mesh);
-    selects.push(mesh);
-
-    geometry = new THREE.IcosahedronGeometry(0.025, 4);
-    material = new THREE.MeshStandardMaterial({ color: 'cyan' });
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.position.set(-0.05, 0.025, 0.08);
-    scene.add(mesh);
-    otherMeshes.push(mesh);
-    selects.push(mesh);
-
-    geometry = new THREE.ConeGeometry(0.025, 0.05, 64);
-    material = new THREE.MeshStandardMaterial({ color: 'yellow' });
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.position.set(-0.05, 0.025, -0.055);
-    scene.add(mesh);
-    otherMeshes.push(mesh);
-    selects.push(mesh);
-
-    geometry = new THREE.PlaneGeometry(1, 1);
-    groundReflector = new ReflectorForSSRPass(geometry, {
-        clipBias: 0.0003,
-        textureWidth: window.innerWidth,
-        textureHeight: window.innerHeight,
-        color: 0x888888,
-        useDepthTexture: true,
-    });
-    groundReflector.material.depthWrite = false;
-    groundReflector.rotation.x = -Math.PI / 2;
-    groundReflector.visible = false;
-    scene.add(groundReflector);
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: false });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.target.set(0, 0.0635, 0);
-    controls.update();
-    controls.enabled = !params.autoRotate;
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // composer
-
-    composer = new EffectComposer(renderer);
-    ssrPass = new SSRPass({
-        renderer,
-        scene,
-        camera,
-        width: innerWidth,
-        height: innerHeight,
-        groundReflector: params.groundReflector ? groundReflector : null,
-        selects: params.groundReflector ? selects : null,
-    });
-
-    composer.addPass(ssrPass);
-    composer.addPass(new OutputPass());
-
-    // GUI
-
-    gui = new GUI({ width: 260 });
-    gui.add(params, 'enableSSR').name('Enable SSR');
-    gui.add(params, 'groundReflector').onChange(() => {
-        if (params.groundReflector) {
-            (ssrPass.groundReflector = groundReflector), (ssrPass.selects = selects);
-        } else {
-            (ssrPass.groundReflector = null), (ssrPass.selects = null);
-        }
-    });
-    ssrPass.thickness = 0.018;
-    gui.add(ssrPass, 'thickness').min(0).max(0.1).step(0.0001);
-    ssrPass.infiniteThick = false;
-    gui.add(ssrPass, 'infiniteThick');
-    gui.add(params, 'autoRotate').onChange(() => {
-        controls.enabled = !params.autoRotate;
-    });
-
-    const folder = gui.addFolder('more settings');
-    folder.add(ssrPass, 'fresnel').onChange(() => {
-        groundReflector.fresnel = ssrPass.fresnel;
-    });
-    folder.add(ssrPass, 'distanceAttenuation').onChange(() => {
-        groundReflector.distanceAttenuation = ssrPass.distanceAttenuation;
-    });
-    ssrPass.maxDistance = 0.1;
-    groundReflector.maxDistance = ssrPass.maxDistance;
-    folder
-        .add(ssrPass, 'maxDistance')
-        .min(0)
-        .max(0.5)
-        .step(0.001)
-        .onChange(() => {
-            groundReflector.maxDistance = ssrPass.maxDistance;
-        });
-    folder.add(params, 'otherMeshes').onChange(() => {
-        if (params.otherMeshes) {
-            otherMeshes.forEach(mesh => (mesh.visible = true));
-        } else {
-            otherMeshes.forEach(mesh => (mesh.visible = false));
-        }
-    });
-    folder.add(ssrPass, 'bouncing');
-    folder
-        .add(ssrPass, 'output', {
-            Default: SSRPass.OUTPUT.Default,
-            'SSR Only': SSRPass.OUTPUT.SSR,
-            Beauty: SSRPass.OUTPUT.Beauty,
-            Depth: SSRPass.OUTPUT.Depth,
-            Normal: SSRPass.OUTPUT.Normal,
-            Metalness: SSRPass.OUTPUT.Metalness,
-        })
-        .onChange(function (value) {
-            ssrPass.output = value;
-        });
-    ssrPass.opacity = 1;
-    groundReflector.opacity = ssrPass.opacity;
-    folder
-        .add(ssrPass, 'opacity')
-        .min(0)
-        .max(1)
-        .onChange(() => {
-            groundReflector.opacity = ssrPass.opacity;
-        });
-    folder.add(ssrPass, 'blur');
-    // folder.open()
-    // gui.close()
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-    groundReflector.getRenderTarget().setSize(window.innerWidth, window.innerHeight);
-    groundReflector.resolution.set(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    if (params.autoRotate) {
-        const timer = Date.now() * 0.0003;
-
-        camera.position.x = Math.sin(timer) * 0.5;
-        camera.position.y = 0.2135;
-        camera.position.z = Math.cos(timer) * 0.5;
-        camera.lookAt(0, 0.0635, 0);
-    } else {
-        controls.update();
-    }
-
-    if (params.enableSSR) {
-        // TODO: groundReflector has full ground info, need use it to solve reflection gaps problem on objects when camera near ground.
-        // TODO: the normal and depth info where groundReflector reflected need to be changed.
-        composer.render();
-    } else {
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgl_postprocessing_taa.ts b/examples-testing/examples/webgl_postprocessing_taa.ts
deleted file mode 100644
index 11a986741..000000000
--- a/examples-testing/examples/webgl_postprocessing_taa.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, scene, renderer, composer, taaRenderPass, renderPass;
-let gui, stats;
-let index = 0;
-
-const param = { TAAEnabled: '1', TAASampleLevel: 0 };
-
-init();
-
-clearGui();
-
-function clearGui() {
-    if (gui) gui.destroy();
-
-    gui = new GUI();
-
-    gui.add(param, 'TAAEnabled', {
-        Disabled: '0',
-        Enabled: '1',
-    }).onFinishChange(function () {
-        if (taaRenderPass) {
-            taaRenderPass.enabled = param.TAAEnabled === '1';
-            renderPass.enabled = param.TAAEnabled !== '1';
-        }
-    });
-
-    gui.add(param, 'TAASampleLevel', {
-        'Level 0: 1 Sample': 0,
-        'Level 1: 2 Samples': 1,
-        'Level 2: 4 Samples': 2,
-        'Level 3: 8 Samples': 3,
-        'Level 4: 16 Samples': 4,
-        'Level 5: 32 Samples': 5,
-    }).onFinishChange(function () {
-        if (taaRenderPass) {
-            taaRenderPass.sampleLevel = param.TAASampleLevel;
-        }
-    });
-
-    gui.open();
-}
-
-function init() {
-    const container = document.getElementById('container');
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 300;
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.BoxGeometry(120, 120, 120);
-    const material1 = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
-
-    const mesh1 = new THREE.Mesh(geometry, material1);
-    mesh1.position.x = -100;
-    scene.add(mesh1);
-
-    const texture = new THREE.TextureLoader().load('textures/brick_diffuse.jpg');
-    texture.minFilter = THREE.NearestFilter;
-    texture.magFilter = THREE.NearestFilter;
-    texture.anisotropy = 1;
-    texture.generateMipmaps = false;
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const material2 = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh2 = new THREE.Mesh(geometry, material2);
-    mesh2.position.x = 100;
-    scene.add(mesh2);
-
-    // postprocessing
-
-    composer = new EffectComposer(renderer);
-
-    taaRenderPass = new TAARenderPass(scene, camera);
-    taaRenderPass.unbiased = false;
-    composer.addPass(taaRenderPass);
-
-    renderPass = new RenderPass(scene, camera);
-    renderPass.enabled = false;
-    composer.addPass(renderPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    index++;
-
-    if (Math.round(index / 200) % 2 === 0) {
-        for (let i = 0; i < scene.children.length; i++) {
-            const child = scene.children[i];
-
-            child.rotation.x += 0.005;
-            child.rotation.y += 0.01;
-        }
-
-        if (taaRenderPass) taaRenderPass.accumulate = false;
-    } else {
-        if (taaRenderPass) taaRenderPass.accumulate = true;
-    }
-
-    composer.render();
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_transition.ts b/examples-testing/examples/webgl_postprocessing_transition.ts
deleted file mode 100644
index d05466131..000000000
--- a/examples-testing/examples/webgl_postprocessing_transition.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import TWEEN from 'three/addons/libs/tween.module.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderTransitionPass } from 'three/addons/postprocessing/RenderTransitionPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let stats;
-let renderer, composer, renderTransitionPass;
-
-const textures = [];
-const clock = new THREE.Clock();
-
-const params = {
-    sceneAnimate: true,
-    transitionAnimate: true,
-    transition: 0,
-    useTexture: true,
-    texture: 5,
-    cycle: true,
-    threshold: 0.1,
-};
-
-const fxSceneA = new FXScene(new THREE.BoxGeometry(2, 2, 2), new THREE.Vector3(0, -0.4, 0), 0xffffff);
-const fxSceneB = new FXScene(new THREE.IcosahedronGeometry(1, 1), new THREE.Vector3(0, 0.2, 0.1), 0x000000);
-
-init();
-
-function init() {
-    initGUI();
-    initTextures();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    composer = new EffectComposer(renderer);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    renderTransitionPass = new RenderTransitionPass(fxSceneA.scene, fxSceneA.camera, fxSceneB.scene, fxSceneB.camera);
-    renderTransitionPass.setTexture(textures[0]);
-    composer.addPass(renderTransitionPass);
-
-    const outputPass = new OutputPass();
-    composer.addPass(outputPass);
-}
-
-window.addEventListener('resize', onWindowResize);
-
-function onWindowResize() {
-    fxSceneA.resize();
-    fxSceneB.resize();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-new TWEEN.Tween(params)
-    .to({ transition: 1 }, 1500)
-    .onUpdate(function () {
-        renderTransitionPass.setTransition(params.transition);
-
-        // Change the current alpha texture after each transition
-        if (params.cycle) {
-            if (params.transition == 0 || params.transition == 1) {
-                params.texture = (params.texture + 1) % textures.length;
-                renderTransitionPass.setTexture(textures[params.texture]);
-            }
-        }
-    })
-    .repeat(Infinity)
-    .delay(2000)
-    .yoyo(true)
-    .start();
-
-function animate() {
-    // Transition animation
-    if (params.transitionAnimate) TWEEN.update();
-
-    const delta = clock.getDelta();
-    fxSceneA.update(delta);
-    fxSceneB.update(delta);
-
-    render();
-    stats.update();
-}
-
-function initTextures() {
-    const loader = new THREE.TextureLoader();
-
-    for (let i = 0; i < 6; i++) {
-        textures[i] = loader.load('textures/transition/transition' + (i + 1) + '.png');
-    }
-}
-
-function initGUI() {
-    const gui = new GUI();
-
-    gui.add(params, 'sceneAnimate').name('Animate scene');
-    gui.add(params, 'transitionAnimate').name('Animate transition');
-    gui.add(params, 'transition', 0, 1, 0.01)
-        .onChange(function (value) {
-            renderTransitionPass.setTransition(value);
-        })
-        .listen();
-
-    gui.add(params, 'useTexture').onChange(function (value) {
-        renderTransitionPass.useTexture(value);
-    });
-
-    gui.add(params, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 })
-        .onChange(function (value) {
-            renderTransitionPass.setTexture(textures[value]);
-        })
-        .listen();
-
-    gui.add(params, 'cycle');
-
-    gui.add(params, 'threshold', 0, 1, 0.01).onChange(function (value) {
-        renderTransitionPass.setTextureThreshold(value);
-    });
-}
-
-function render() {
-    // Prevent render both scenes when it's not necessary
-    if (params.transition === 0) {
-        renderer.render(fxSceneB.scene, fxSceneB.camera);
-    } else if (params.transition === 1) {
-        renderer.render(fxSceneA.scene, fxSceneA.camera);
-    } else {
-        // When 0 < transition < 1 render transition between two scenes
-        composer.render();
-    }
-}
-
-function FXScene(geometry, rotationSpeed, backgroundColor) {
-    const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 20;
-
-    // Setup scene
-    const scene = new THREE.Scene();
-    scene.background = new THREE.Color(backgroundColor);
-    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 1, 4);
-    scene.add(light);
-
-    this.rotationSpeed = rotationSpeed;
-
-    const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
-    const material = new THREE.MeshPhongMaterial({ color: color, flatShading: true });
-    const mesh = generateInstancedMesh(geometry, material, 500);
-    scene.add(mesh);
-
-    this.scene = scene;
-    this.camera = camera;
-    this.mesh = mesh;
-
-    this.update = function (delta) {
-        if (params.sceneAnimate) {
-            mesh.rotation.x += this.rotationSpeed.x * delta;
-            mesh.rotation.y += this.rotationSpeed.y * delta;
-            mesh.rotation.z += this.rotationSpeed.z * delta;
-        }
-    };
-
-    this.resize = function () {
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-    };
-}
-
-function generateInstancedMesh(geometry, material, count) {
-    const mesh = new THREE.InstancedMesh(geometry, material, count);
-
-    const dummy = new THREE.Object3D();
-    const color = new THREE.Color();
-
-    for (let i = 0; i < count; i++) {
-        dummy.position.x = Math.random() * 100 - 50;
-        dummy.position.y = Math.random() * 60 - 30;
-        dummy.position.z = Math.random() * 80 - 40;
-
-        dummy.rotation.x = Math.random() * 2 * Math.PI;
-        dummy.rotation.y = Math.random() * 2 * Math.PI;
-        dummy.rotation.z = Math.random() * 2 * Math.PI;
-
-        dummy.scale.x = Math.random() * 2 + 1;
-
-        if (geometry.type === 'BoxGeometry') {
-            dummy.scale.y = Math.random() * 2 + 1;
-            dummy.scale.z = Math.random() * 2 + 1;
-        } else {
-            dummy.scale.y = dummy.scale.x;
-            dummy.scale.z = dummy.scale.x;
-        }
-
-        dummy.updateMatrix();
-
-        mesh.setMatrixAt(i, dummy.matrix);
-        mesh.setColorAt(i, color.setScalar(0.1 + 0.9 * Math.random()));
-    }
-
-    return mesh;
-}
diff --git a/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts b/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
deleted file mode 100644
index 53ec2fe2f..000000000
--- a/examples-testing/examples/webgl_postprocessing_unreal_bloom.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, stats;
-let composer, renderer, mixer, clock;
-
-const params = {
-    threshold: 0,
-    strength: 1,
-    radius: 0,
-    exposure: 1,
-};
-
-init();
-
-async function init() {
-    const container = document.getElementById('container');
-
-    clock = new THREE.Clock();
-
-    const scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(-5, 2.5, -3.5);
-    scene.add(camera);
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const pointLight = new THREE.PointLight(0xffffff, 100);
-    camera.add(pointLight);
-
-    const loader = new GLTFLoader();
-    const gltf = await loader.loadAsync('models/gltf/PrimaryIonDrive.glb');
-
-    const model = gltf.scene;
-    scene.add(model);
-
-    mixer = new THREE.AnimationMixer(model);
-    const clip = gltf.animations[0];
-    mixer.clipAction(clip.optimize()).play();
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const renderScene = new RenderPass(scene, camera);
-
-    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
-    bloomPass.threshold = params.threshold;
-    bloomPass.strength = params.strength;
-    bloomPass.radius = params.radius;
-
-    const outputPass = new OutputPass();
-
-    composer = new EffectComposer(renderer);
-    composer.addPass(renderScene);
-    composer.addPass(bloomPass);
-    composer.addPass(outputPass);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI * 0.5;
-    controls.minDistance = 3;
-    controls.maxDistance = 8;
-
-    //
-
-    const gui = new GUI();
-
-    const bloomFolder = gui.addFolder('bloom');
-
-    bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
-        bloomPass.threshold = Number(value);
-    });
-
-    bloomFolder.add(params, 'strength', 0.0, 3.0).onChange(function (value) {
-        bloomPass.strength = Number(value);
-    });
-
-    gui.add(params, 'radius', 0.0, 1.0)
-        .step(0.01)
-        .onChange(function (value) {
-            bloomPass.radius = Number(value);
-        });
-
-    const toneMappingFolder = gui.addFolder('tone mapping');
-
-    toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
-        renderer.toneMappingExposure = Math.pow(value, 4.0);
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-    composer.setSize(width, height);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    mixer.update(delta);
-
-    stats.update();
-
-    composer.render();
-}
diff --git a/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts b/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
deleted file mode 100644
index d633806ee..000000000
--- a/examples-testing/examples/webgl_postprocessing_unreal_bloom_selective.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
-import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-const BLOOM_SCENE = 1;
-
-const bloomLayer = new THREE.Layers();
-bloomLayer.set(BLOOM_SCENE);
-
-const params = {
-    threshold: 0,
-    strength: 1,
-    radius: 0.5,
-    exposure: 1,
-};
-
-const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
-const materials = {};
-
-const renderer = new THREE.WebGLRenderer({ antialias: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.toneMapping = THREE.ReinhardToneMapping;
-document.body.appendChild(renderer.domElement);
-
-const scene = new THREE.Scene();
-
-const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
-camera.position.set(0, 0, 20);
-camera.lookAt(0, 0, 0);
-
-const controls = new OrbitControls(camera, renderer.domElement);
-controls.maxPolarAngle = Math.PI * 0.5;
-controls.minDistance = 1;
-controls.maxDistance = 100;
-controls.addEventListener('change', render);
-
-const renderScene = new RenderPass(scene, camera);
-
-const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
-bloomPass.threshold = params.threshold;
-bloomPass.strength = params.strength;
-bloomPass.radius = params.radius;
-
-const bloomComposer = new EffectComposer(renderer);
-bloomComposer.renderToScreen = false;
-bloomComposer.addPass(renderScene);
-bloomComposer.addPass(bloomPass);
-
-const mixPass = new ShaderPass(
-    new THREE.ShaderMaterial({
-        uniforms: {
-            baseTexture: { value: null },
-            bloomTexture: { value: bloomComposer.renderTarget2.texture },
-        },
-        vertexShader: document.getElementById('vertexshader').textContent,
-        fragmentShader: document.getElementById('fragmentshader').textContent,
-        defines: {},
-    }),
-    'baseTexture',
-);
-mixPass.needsSwap = true;
-
-const outputPass = new OutputPass();
-
-const finalComposer = new EffectComposer(renderer);
-finalComposer.addPass(renderScene);
-finalComposer.addPass(mixPass);
-finalComposer.addPass(outputPass);
-
-const raycaster = new THREE.Raycaster();
-
-const mouse = new THREE.Vector2();
-
-window.addEventListener('pointerdown', onPointerDown);
-
-const gui = new GUI();
-
-const bloomFolder = gui.addFolder('bloom');
-
-bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
-    bloomPass.threshold = Number(value);
-    render();
-});
-
-bloomFolder.add(params, 'strength', 0.0, 3).onChange(function (value) {
-    bloomPass.strength = Number(value);
-    render();
-});
-
-bloomFolder
-    .add(params, 'radius', 0.0, 1.0)
-    .step(0.01)
-    .onChange(function (value) {
-        bloomPass.radius = Number(value);
-        render();
-    });
-
-const toneMappingFolder = gui.addFolder('tone mapping');
-
-toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
-    renderer.toneMappingExposure = Math.pow(value, 4.0);
-    render();
-});
-
-setupScene();
-
-function onPointerDown(event) {
-    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-    raycaster.setFromCamera(mouse, camera);
-    const intersects = raycaster.intersectObjects(scene.children, false);
-    if (intersects.length > 0) {
-        const object = intersects[0].object;
-        object.layers.toggle(BLOOM_SCENE);
-        render();
-    }
-}
-
-window.onresize = function () {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-
-    bloomComposer.setSize(width, height);
-    finalComposer.setSize(width, height);
-
-    render();
-};
-
-function setupScene() {
-    scene.traverse(disposeMaterial);
-    scene.children.length = 0;
-
-    const geometry = new THREE.IcosahedronGeometry(1, 15);
-
-    for (let i = 0; i < 50; i++) {
-        const color = new THREE.Color();
-        color.setHSL(Math.random(), 0.7, Math.random() * 0.2 + 0.05);
-
-        const material = new THREE.MeshBasicMaterial({ color: color });
-        const sphere = new THREE.Mesh(geometry, material);
-        sphere.position.x = Math.random() * 10 - 5;
-        sphere.position.y = Math.random() * 10 - 5;
-        sphere.position.z = Math.random() * 10 - 5;
-        sphere.position.normalize().multiplyScalar(Math.random() * 4.0 + 2.0);
-        sphere.scale.setScalar(Math.random() * Math.random() + 0.5);
-        scene.add(sphere);
-
-        if (Math.random() < 0.25) sphere.layers.enable(BLOOM_SCENE);
-    }
-
-    render();
-}
-
-function disposeMaterial(obj) {
-    if (obj.material) {
-        obj.material.dispose();
-    }
-}
-
-function render() {
-    scene.traverse(darkenNonBloomed);
-    bloomComposer.render();
-    scene.traverse(restoreMaterial);
-
-    // render the entire scene, then render bloom scene on top
-    finalComposer.render();
-}
-
-function darkenNonBloomed(obj) {
-    if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
-        materials[obj.uuid] = obj.material;
-        obj.material = darkMaterial;
-    }
-}
-
-function restoreMaterial(obj) {
-    if (materials[obj.uuid]) {
-        obj.material = materials[obj.uuid];
-        delete materials[obj.uuid];
-    }
-}
diff --git a/examples-testing/examples/webgl_raycaster_sprite.ts b/examples-testing/examples/webgl_raycaster_sprite.ts
deleted file mode 100644
index f35d5de17..000000000
--- a/examples-testing/examples/webgl_raycaster_sprite.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let renderer, scene, camera;
-let group;
-
-let selectedObject = null;
-const raycaster = new THREE.Raycaster();
-const pointer = new THREE.Vector2();
-
-init();
-
-function init() {
-    // init renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // init scene
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    // init camera
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(15, 15, 15);
-    camera.lookAt(scene.position);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 15;
-    controls.maxDistance = 250;
-
-    // add sprites
-
-    const sprite1 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f' }));
-    sprite1.position.set(6, 5, 5);
-    sprite1.scale.set(2, 5, 1);
-    group.add(sprite1);
-
-    const sprite2 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f', sizeAttenuation: false }));
-    sprite2.material.rotation = (Math.PI / 3) * 4;
-    sprite2.position.set(8, -2, 2);
-    sprite2.center.set(0.5, 0);
-    sprite2.scale.set(0.1, 0.5, 0.1);
-    group.add(sprite2);
-
-    const group2 = new THREE.Object3D();
-    group2.scale.set(1, 2, 1);
-    group2.position.set(-5, 0, 0);
-    group2.rotation.set(Math.PI / 2, 0, 0);
-    group.add(group2);
-
-    const sprite3 = new THREE.Sprite(new THREE.SpriteMaterial({ color: '#69f' }));
-    sprite3.position.set(0, 2, 5);
-    sprite3.scale.set(10, 2, 3);
-    sprite3.center.set(-0.1, 0);
-    sprite3.material.rotation = Math.PI / 3;
-    group2.add(sprite3);
-
-    window.addEventListener('resize', onWindowResize);
-    document.addEventListener('pointermove', onPointerMove);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerMove(event) {
-    if (selectedObject) {
-        selectedObject.material.color.set('#69f');
-        selectedObject = null;
-    }
-
-    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
-    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-    raycaster.setFromCamera(pointer, camera);
-
-    const intersects = raycaster.intersectObject(group, true);
-
-    if (intersects.length > 0) {
-        const res = intersects.filter(function (res) {
-            return res && res.object;
-        })[0];
-
-        if (res && res.object) {
-            selectedObject = res.object;
-            selectedObject.material.color.set('#f00');
-        }
-    }
-}
diff --git a/examples-testing/examples/webgl_raycaster_texture.ts b/examples-testing/examples/webgl_raycaster_texture.ts
deleted file mode 100644
index 72c7054dc..000000000
--- a/examples-testing/examples/webgl_raycaster_texture.ts
+++ /dev/null
@@ -1,286 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const WRAPPING = {
-    RepeatWrapping: THREE.RepeatWrapping,
-    ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
-    MirroredRepeatWrapping: THREE.MirroredRepeatWrapping,
-};
-
-const params = {
-    wrapS: THREE.RepeatWrapping,
-    wrapT: THREE.RepeatWrapping,
-    offsetX: 0,
-    offsetY: 0,
-    repeatX: 1,
-    repeatY: 1,
-    rotation: 0,
-};
-
-function CanvasTexture(parentTexture) {
-    this._canvas = document.createElement('canvas');
-    this._canvas.width = this._canvas.height = 1024;
-    this._context2D = this._canvas.getContext('2d');
-
-    if (parentTexture) {
-        this._parentTexture.push(parentTexture);
-        parentTexture.image = this._canvas;
-    }
-
-    const that = this;
-    this._background = document.createElement('img');
-    this._background.addEventListener('load', function () {
-        that._canvas.width = that._background.naturalWidth;
-        that._canvas.height = that._background.naturalHeight;
-
-        that._crossRadius = Math.ceil(Math.min(that._canvas.width, that._canvas.height / 30));
-        that._crossMax = Math.ceil(0.70710678 * that._crossRadius);
-        that._crossMin = Math.ceil(that._crossMax / 10);
-        that._crossThickness = Math.ceil(that._crossMax / 10);
-
-        that._draw();
-    });
-    this._background.crossOrigin = '';
-    this._background.src = 'textures/uv_grid_opengl.jpg';
-
-    this._draw();
-}
-
-CanvasTexture.prototype = {
-    constructor: CanvasTexture,
-
-    _canvas: null,
-    _context2D: null,
-    _xCross: 0,
-    _yCross: 0,
-
-    _crossRadius: 57,
-    _crossMax: 40,
-    _crossMin: 4,
-    _crossThickness: 4,
-
-    _parentTexture: [],
-
-    addParent: function (parentTexture) {
-        if (this._parentTexture.indexOf(parentTexture) === -1) {
-            this._parentTexture.push(parentTexture);
-            parentTexture.image = this._canvas;
-        }
-    },
-
-    setCrossPosition: function (x, y) {
-        this._xCross = x * this._canvas.width;
-        this._yCross = y * this._canvas.height;
-
-        this._draw();
-    },
-
-    _draw: function () {
-        if (!this._context2D) return;
-
-        this._context2D.clearRect(0, 0, this._canvas.width, this._canvas.height);
-
-        // Background.
-        this._context2D.drawImage(this._background, 0, 0);
-
-        // Yellow cross.
-        this._context2D.lineWidth = this._crossThickness * 3;
-        this._context2D.strokeStyle = '#FFFF00';
-
-        this._context2D.beginPath();
-        this._context2D.moveTo(this._xCross - this._crossMax - 2, this._yCross - this._crossMax - 2);
-        this._context2D.lineTo(this._xCross - this._crossMin, this._yCross - this._crossMin);
-
-        this._context2D.moveTo(this._xCross + this._crossMin, this._yCross + this._crossMin);
-        this._context2D.lineTo(this._xCross + this._crossMax + 2, this._yCross + this._crossMax + 2);
-
-        this._context2D.moveTo(this._xCross - this._crossMax - 2, this._yCross + this._crossMax + 2);
-        this._context2D.lineTo(this._xCross - this._crossMin, this._yCross + this._crossMin);
-
-        this._context2D.moveTo(this._xCross + this._crossMin, this._yCross - this._crossMin);
-        this._context2D.lineTo(this._xCross + this._crossMax + 2, this._yCross - this._crossMax - 2);
-
-        this._context2D.stroke();
-
-        for (let i = 0; i < this._parentTexture.length; i++) {
-            this._parentTexture[i].needsUpdate = true;
-        }
-    },
-};
-
-const width = window.innerWidth;
-const height = window.innerHeight;
-
-let canvas;
-let planeTexture, cubeTexture, circleTexture;
-
-let container;
-
-let camera, scene, renderer;
-
-const raycaster = new THREE.Raycaster();
-const mouse = new THREE.Vector2();
-const onClickPosition = new THREE.Vector2();
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xeeeeee);
-
-    camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000);
-    camera.position.x = -30;
-    camera.position.y = 40;
-    camera.position.z = 50;
-    camera.lookAt(scene.position);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(width, height);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // A cube, in the middle.
-    cubeTexture = new THREE.Texture(undefined, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping);
-    cubeTexture.colorSpace = THREE.SRGBColorSpace;
-    canvas = new CanvasTexture(cubeTexture);
-    const cubeMaterial = new THREE.MeshBasicMaterial({ map: cubeTexture });
-    const cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
-    let uvs = cubeGeometry.attributes.uv.array;
-    // Set a specific texture mapping.
-    for (let i = 0; i < uvs.length; i++) {
-        uvs[i] *= 2;
-    }
-
-    const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
-    cube.position.x = 4;
-    cube.position.y = -5;
-    cube.position.z = 0;
-    scene.add(cube);
-
-    // A plane on the left
-
-    planeTexture = new THREE.Texture(
-        undefined,
-        THREE.UVMapping,
-        THREE.MirroredRepeatWrapping,
-        THREE.MirroredRepeatWrapping,
-    );
-    planeTexture.colorSpace = THREE.SRGBColorSpace;
-    canvas.addParent(planeTexture);
-    const planeMaterial = new THREE.MeshBasicMaterial({ map: planeTexture });
-    const planeGeometry = new THREE.PlaneGeometry(25, 25, 1, 1);
-    uvs = planeGeometry.attributes.uv.array;
-
-    // Set a specific texture mapping.
-
-    for (let i = 0; i < uvs.length; i++) {
-        uvs[i] *= 2;
-    }
-
-    const plane = new THREE.Mesh(planeGeometry, planeMaterial);
-    plane.position.x = -16;
-    plane.position.y = -5;
-    plane.position.z = 0;
-    scene.add(plane);
-
-    // A circle on the right.
-
-    circleTexture = new THREE.Texture(undefined, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping);
-    circleTexture.colorSpace = THREE.SRGBColorSpace;
-    canvas.addParent(circleTexture);
-    const circleMaterial = new THREE.MeshBasicMaterial({ map: circleTexture });
-    const circleGeometry = new THREE.CircleGeometry(25, 40, 0, Math.PI * 2);
-    uvs = circleGeometry.attributes.uv.array;
-
-    // Set a specific texture mapping.
-
-    for (let i = 0; i < uvs.length; i++) {
-        uvs[i] = (uvs[i] - 0.25) * 2;
-    }
-
-    const circle = new THREE.Mesh(circleGeometry, circleMaterial);
-    circle.position.x = 24;
-    circle.position.y = -5;
-    circle.position.z = 0;
-    scene.add(circle);
-
-    window.addEventListener('resize', onWindowResize);
-    container.addEventListener('mousemove', onMouseMove);
-
-    //
-
-    const gui = new GUI();
-    gui.title('Circle Texture Settings');
-
-    gui.add(params, 'wrapS', WRAPPING).onChange(setwrapS);
-    gui.add(params, 'wrapT', WRAPPING).onChange(setwrapT);
-    gui.add(params, 'offsetX', 0, 5);
-    gui.add(params, 'offsetY', 0, 5);
-    gui.add(params, 'repeatX', 0, 5);
-    gui.add(params, 'repeatY', 0, 5);
-    gui.add(params, 'rotation', 0, 2 * Math.PI);
-    gui.open();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onMouseMove(evt) {
-    evt.preventDefault();
-
-    const array = getMousePosition(container, evt.clientX, evt.clientY);
-    onClickPosition.fromArray(array);
-
-    const intersects = getIntersects(onClickPosition, scene.children);
-
-    if (intersects.length > 0 && intersects[0].uv) {
-        const uv = intersects[0].uv;
-        intersects[0].object.material.map.transformUv(uv);
-        canvas.setCrossPosition(uv.x, uv.y);
-    }
-}
-
-function getMousePosition(dom, x, y) {
-    const rect = dom.getBoundingClientRect();
-    return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
-}
-
-function getIntersects(point, objects) {
-    mouse.set(point.x * 2 - 1, -(point.y * 2) + 1);
-
-    raycaster.setFromCamera(mouse, camera);
-
-    return raycaster.intersectObjects(objects, false);
-}
-
-function animate() {
-    // update texture parameters
-
-    circleTexture.offset.x = params.offsetX;
-    circleTexture.offset.y = params.offsetY;
-    circleTexture.repeat.x = params.repeatX;
-    circleTexture.repeat.y = params.repeatY;
-    circleTexture.rotation = params.rotation;
-
-    //
-
-    renderer.render(scene, camera);
-}
-
-function setwrapS(value) {
-    circleTexture.wrapS = value;
-    circleTexture.needsUpdate = true;
-}
-
-function setwrapT(value) {
-    circleTexture.wrapT = value;
-    circleTexture.needsUpdate = true;
-}
diff --git a/examples-testing/examples/webgl_raymarching_reflect.ts b/examples-testing/examples/webgl_raymarching_reflect.ts
deleted file mode 100644
index e5448ebbd..000000000
--- a/examples-testing/examples/webgl_raymarching_reflect.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let dolly, camera, scene, renderer;
-let geometry, material, mesh;
-let stats, clock;
-
-const canvas = document.querySelector('#canvas');
-
-const config = {
-    saveImage: function () {
-        renderer.render(scene, camera);
-        window.open(canvas.toDataURL());
-    },
-    resolution: '512',
-};
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer({ canvas: canvas });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(parseInt(config.resolution), parseInt(config.resolution));
-    renderer.setAnimationLoop(animate);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // THREE.Scene
-    scene = new THREE.Scene();
-
-    dolly = new THREE.Group();
-    scene.add(dolly);
-
-    clock = new THREE.Clock();
-
-    camera = new THREE.PerspectiveCamera(60, canvas.width / canvas.height, 1, 2000);
-    camera.position.z = 4;
-    dolly.add(camera);
-
-    geometry = new THREE.PlaneGeometry(2.0, 2.0);
-    material = new THREE.RawShaderMaterial({
-        uniforms: {
-            resolution: { value: new THREE.Vector2(canvas.width, canvas.height) },
-            cameraWorldMatrix: { value: camera.matrixWorld },
-            cameraProjectionMatrixInverse: { value: camera.projectionMatrixInverse.clone() },
-        },
-        vertexShader: document.getElementById('vertex_shader').textContent,
-        fragmentShader: document.getElementById('fragment_shader').textContent,
-    });
-    mesh = new THREE.Mesh(geometry, material);
-    mesh.frustumCulled = false;
-    scene.add(mesh);
-
-    // Controls
-    const controls = new OrbitControls(camera, canvas);
-    controls.enableZoom = false;
-
-    // GUI
-    const gui = new GUI();
-    gui.add(config, 'saveImage').name('Save Image');
-    gui.add(config, 'resolution', ['256', '512', '800', 'full']).name('Resolution').onChange(onWindowResize);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-}
-
-function onWindowResize() {
-    if (config.resolution === 'full') {
-        renderer.setSize(window.innerWidth, window.innerHeight);
-    } else {
-        renderer.setSize(parseInt(config.resolution), parseInt(config.resolution));
-    }
-
-    camera.aspect = canvas.width / canvas.height;
-    camera.updateProjectionMatrix();
-
-    material.uniforms.resolution.value.set(canvas.width, canvas.height);
-    material.uniforms.cameraProjectionMatrixInverse.value.copy(camera.projectionMatrixInverse);
-}
-
-function animate() {
-    stats.begin();
-
-    const elapsedTime = clock.getElapsedTime();
-
-    dolly.position.z = -elapsedTime;
-
-    renderer.render(scene, camera);
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgl_read_float_buffer.ts b/examples-testing/examples/webgl_read_float_buffer.ts
deleted file mode 100644
index 68452a12a..000000000
--- a/examples-testing/examples/webgl_read_float_buffer.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let cameraRTT, sceneRTT, sceneScreen, renderer, zmesh1, zmesh2;
-
-let mouseX = 0,
-    mouseY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-let rtTexture, material, quad;
-
-let delta = 0.01;
-let valueNode;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    cameraRTT = new THREE.OrthographicCamera(
-        window.innerWidth / -2,
-        window.innerWidth / 2,
-        window.innerHeight / 2,
-        window.innerHeight / -2,
-        -10000,
-        10000,
-    );
-    cameraRTT.position.z = 100;
-
-    //
-
-    sceneRTT = new THREE.Scene();
-    sceneScreen = new THREE.Scene();
-
-    let light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 0, 1).normalize();
-    sceneRTT.add(light);
-
-    light = new THREE.DirectionalLight(0xffd5d5, 4.5);
-    light.position.set(0, 0, -1).normalize();
-    sceneRTT.add(light);
-
-    rtTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
-        minFilter: THREE.LinearFilter,
-        magFilter: THREE.NearestFilter,
-        format: THREE.RGBAFormat,
-        type: THREE.FloatType,
-    });
-
-    material = new THREE.ShaderMaterial({
-        uniforms: { time: { value: 0.0 } },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragment_shader_pass_1').textContent,
-    });
-
-    const materialScreen = new THREE.ShaderMaterial({
-        uniforms: { tDiffuse: { value: rtTexture.texture } },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragment_shader_screen').textContent,
-
-        depthWrite: false,
-    });
-
-    const plane = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
-
-    quad = new THREE.Mesh(plane, material);
-    quad.position.z = -100;
-    sceneRTT.add(quad);
-
-    const geometry = new THREE.TorusGeometry(100, 25, 15, 30);
-
-    const mat1 = new THREE.MeshPhongMaterial({ color: 0x9c9c9c, specular: 0xffaa00, shininess: 5 });
-    const mat2 = new THREE.MeshPhongMaterial({ color: 0x9c0000, specular: 0xff2200, shininess: 5 });
-
-    zmesh1 = new THREE.Mesh(geometry, mat1);
-    zmesh1.position.set(0, 0, 100);
-    zmesh1.scale.set(1.5, 1.5, 1.5);
-    sceneRTT.add(zmesh1);
-
-    zmesh2 = new THREE.Mesh(geometry, mat2);
-    zmesh2.position.set(0, 150, 100);
-    zmesh2.scale.set(0.75, 0.75, 0.75);
-    sceneRTT.add(zmesh2);
-
-    quad = new THREE.Mesh(plane, materialScreen);
-    quad.position.z = -100;
-    sceneScreen.add(quad);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    valueNode = document.getElementById('values');
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.0015;
-
-    if (zmesh1 && zmesh2) {
-        zmesh1.rotation.y = -time;
-        zmesh2.rotation.y = -time + Math.PI / 2;
-    }
-
-    if (material.uniforms['time'].value > 1 || material.uniforms['time'].value < 0) {
-        delta *= -1;
-    }
-
-    material.uniforms['time'].value += delta;
-
-    renderer.clear();
-
-    // Render first scene into texture
-
-    renderer.setRenderTarget(rtTexture);
-    renderer.clear();
-    renderer.render(sceneRTT, cameraRTT);
-
-    // Render full screen quad with generated texture
-
-    renderer.setRenderTarget(null);
-    renderer.render(sceneScreen, cameraRTT);
-
-    const read = new Float32Array(4);
-    renderer.readRenderTargetPixels(rtTexture, windowHalfX + mouseX, windowHalfY - mouseY, 1, 1, read);
-
-    valueNode.innerHTML = 'r:' + read[0] + '<br/>g:' + read[1] + '<br/>b:' + read[2];
-}
diff --git a/examples-testing/examples/webgl_refraction.ts b/examples-testing/examples/webgl_refraction.ts
deleted file mode 100644
index 572575afa..000000000
--- a/examples-testing/examples/webgl_refraction.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Refractor } from 'three/addons/objects/Refractor.js';
-import { WaterRefractionShader } from 'three/addons/shaders/WaterRefractionShader.js';
-
-let camera, scene, renderer, clock;
-
-let refractor, smallSphere;
-
-init();
-
-async function init() {
-    const container = document.getElementById('container');
-
-    clock = new THREE.Clock();
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.set(0, 75, 160);
-
-    // refractor
-
-    const refractorGeometry = new THREE.PlaneGeometry(90, 90);
-
-    refractor = new Refractor(refractorGeometry, {
-        color: 0xcbcbcb,
-        textureWidth: 1024,
-        textureHeight: 1024,
-        shader: WaterRefractionShader,
-    });
-
-    refractor.position.set(0, 50, 0);
-
-    scene.add(refractor);
-
-    // load dudv map for distortion effect
-
-    const loader = new THREE.TextureLoader();
-    const dudvMap = await loader.loadAsync('textures/waterdudv.jpg');
-
-    dudvMap.wrapS = dudvMap.wrapT = THREE.RepeatWrapping;
-    refractor.material.uniforms.tDudv.value = dudvMap;
-
-    //
-
-    const geometry = new THREE.IcosahedronGeometry(5, 0);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x333333, flatShading: true });
-    smallSphere = new THREE.Mesh(geometry, material);
-    scene.add(smallSphere);
-
-    // walls
-    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
-
-    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeTop.position.y = 100;
-    planeTop.rotateX(Math.PI / 2);
-    scene.add(planeTop);
-
-    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeBottom.rotateX(-Math.PI / 2);
-    scene.add(planeBottom);
-
-    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
-    planeBack.position.z = -50;
-    planeBack.position.y = 50;
-    scene.add(planeBack);
-
-    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
-    planeRight.position.x = 50;
-    planeRight.position.y = 50;
-    planeRight.rotateY(-Math.PI / 2);
-    scene.add(planeRight);
-
-    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
-    planeLeft.position.x = -50;
-    planeLeft.position.y = 50;
-    planeLeft.rotateY(Math.PI / 2);
-    scene.add(planeLeft);
-
-    // lights
-    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
-    mainLight.position.y = 60;
-    scene.add(mainLight);
-
-    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
-    greenLight.position.set(550, 50, 0);
-    scene.add(greenLight);
-
-    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
-    redLight.position.set(-550, 50, 0);
-    scene.add(redLight);
-
-    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
-    blueLight.position.set(0, 50, 550);
-    scene.add(blueLight);
-
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 40, 0);
-    controls.maxDistance = 400;
-    controls.minDistance = 10;
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = clock.getElapsedTime();
-
-    refractor.material.uniforms.time.value = time;
-
-    smallSphere.position.set(Math.cos(time) * 30, Math.abs(Math.cos(time * 2)) * 20 + 5, Math.sin(time) * 30);
-    smallSphere.rotation.y = Math.PI / 2 - time;
-    smallSphere.rotation.z = time * 8;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_rtt.ts b/examples-testing/examples/webgl_rtt.ts
deleted file mode 100644
index 9f16fdab8..000000000
--- a/examples-testing/examples/webgl_rtt.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let cameraRTT, camera, sceneRTT, sceneScreen, scene, renderer, zmesh1, zmesh2;
-
-let mouseX = 0,
-    mouseY = 0;
-
-const windowHalfX = window.innerWidth / 2;
-const windowHalfY = window.innerHeight / 2;
-
-let rtTexture, material, quad;
-
-let delta = 0.01;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 100;
-
-    cameraRTT = new THREE.OrthographicCamera(
-        window.innerWidth / -2,
-        window.innerWidth / 2,
-        window.innerHeight / 2,
-        window.innerHeight / -2,
-        -10000,
-        10000,
-    );
-    cameraRTT.position.z = 100;
-
-    //
-
-    scene = new THREE.Scene();
-    sceneRTT = new THREE.Scene();
-    sceneScreen = new THREE.Scene();
-
-    let light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 0, 1).normalize();
-    sceneRTT.add(light);
-
-    light = new THREE.DirectionalLight(0xffd5d5, 4.5);
-    light.position.set(0, 0, -1).normalize();
-    sceneRTT.add(light);
-
-    rtTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
-
-    material = new THREE.ShaderMaterial({
-        uniforms: { time: { value: 0.0 } },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragment_shader_pass_1').textContent,
-    });
-
-    const materialScreen = new THREE.ShaderMaterial({
-        uniforms: { tDiffuse: { value: rtTexture.texture } },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragment_shader_screen').textContent,
-
-        depthWrite: false,
-    });
-
-    const plane = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
-
-    quad = new THREE.Mesh(plane, material);
-    quad.position.z = -100;
-    sceneRTT.add(quad);
-
-    const torusGeometry = new THREE.TorusGeometry(100, 25, 15, 30);
-
-    const mat1 = new THREE.MeshPhongMaterial({ color: 0x9c9c9c, specular: 0xffaa00, shininess: 5 });
-    const mat2 = new THREE.MeshPhongMaterial({ color: 0x9c0000, specular: 0xff2200, shininess: 5 });
-
-    zmesh1 = new THREE.Mesh(torusGeometry, mat1);
-    zmesh1.position.set(0, 0, 100);
-    zmesh1.scale.set(1.5, 1.5, 1.5);
-    sceneRTT.add(zmesh1);
-
-    zmesh2 = new THREE.Mesh(torusGeometry, mat2);
-    zmesh2.position.set(0, 150, 100);
-    zmesh2.scale.set(0.75, 0.75, 0.75);
-    sceneRTT.add(zmesh2);
-
-    quad = new THREE.Mesh(plane, materialScreen);
-    quad.position.z = -100;
-    sceneScreen.add(quad);
-
-    const n = 5,
-        geometry = new THREE.SphereGeometry(10, 64, 32),
-        material2 = new THREE.MeshBasicMaterial({ color: 0xffffff, map: rtTexture.texture });
-
-    for (let j = 0; j < n; j++) {
-        for (let i = 0; i < n; i++) {
-            const mesh = new THREE.Mesh(geometry, material2);
-
-            mesh.position.x = (i - (n - 1) / 2) * 20;
-            mesh.position.y = (j - (n - 1) / 2) * 20;
-            mesh.position.z = 0;
-
-            mesh.rotation.y = -Math.PI / 2;
-
-            scene.add(mesh);
-        }
-    }
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    container.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-//
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = Date.now() * 0.0015;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    if (zmesh1 && zmesh2) {
-        zmesh1.rotation.y = -time;
-        zmesh2.rotation.y = -time + Math.PI / 2;
-    }
-
-    if (material.uniforms['time'].value > 1 || material.uniforms['time'].value < 0) {
-        delta *= -1;
-    }
-
-    material.uniforms['time'].value += delta;
-
-    // Render first scene into texture
-
-    renderer.setRenderTarget(rtTexture);
-    renderer.clear();
-    renderer.render(sceneRTT, cameraRTT);
-
-    // Render full screen quad with generated texture
-
-    renderer.setRenderTarget(null);
-    renderer.clear();
-    renderer.render(sceneScreen, cameraRTT);
-
-    // Render second scene to screen
-    // (using first scene as regular texture)
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shader.ts b/examples-testing/examples/webgl_shader.ts
deleted file mode 100644
index 47a6c7ece..000000000
--- a/examples-testing/examples/webgl_shader.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-
-let uniforms;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.PlaneGeometry(2, 2);
-
-    uniforms = {
-        time: { value: 1.0 },
-    };
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-    });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    uniforms['time'].value = performance.now() / 1000;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shader_lava.ts b/examples-testing/examples/webgl_shader_lava.ts
deleted file mode 100644
index 973a580eb..000000000
--- a/examples-testing/examples/webgl_shader_lava.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import * as THREE from 'three';
-
-import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
-import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
-import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
-
-let camera, renderer, composer, clock;
-
-let uniforms, mesh;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 3000);
-    camera.position.z = 4;
-
-    const scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const cloudTexture = textureLoader.load('textures/lava/cloud.png');
-    const lavaTexture = textureLoader.load('textures/lava/lavatile.jpg');
-
-    lavaTexture.colorSpace = THREE.SRGBColorSpace;
-
-    cloudTexture.wrapS = cloudTexture.wrapT = THREE.RepeatWrapping;
-    lavaTexture.wrapS = lavaTexture.wrapT = THREE.RepeatWrapping;
-
-    uniforms = {
-        fogDensity: { value: 0.45 },
-        fogColor: { value: new THREE.Vector3(0, 0, 0) },
-        time: { value: 1.0 },
-        uvScale: { value: new THREE.Vector2(3.0, 1.0) },
-        texture1: { value: cloudTexture },
-        texture2: { value: lavaTexture },
-    };
-
-    const size = 0.65;
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: uniforms,
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-    });
-
-    mesh = new THREE.Mesh(new THREE.TorusGeometry(size, 0.3, 30, 30), material);
-    mesh.rotation.x = 0.3;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const renderModel = new RenderPass(scene, camera);
-    const effectBloom = new BloomPass(1.25);
-    const outputPass = new OutputPass();
-
-    composer = new EffectComposer(renderer);
-
-    composer.addPass(renderModel);
-    composer.addPass(effectBloom);
-    composer.addPass(outputPass);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    composer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const delta = 5 * clock.getDelta();
-
-    uniforms['time'].value += 0.2 * delta;
-
-    mesh.rotation.y += 0.0125 * delta;
-    mesh.rotation.x += 0.05 * delta;
-
-    renderer.clear();
-    composer.render(0.01);
-}
diff --git a/examples-testing/examples/webgl_shaders_ocean.ts b/examples-testing/examples/webgl_shaders_ocean.ts
deleted file mode 100644
index 8b0f9a738..000000000
--- a/examples-testing/examples/webgl_shaders_ocean.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Water } from 'three/addons/objects/Water.js';
-import { Sky } from 'three/addons/objects/Sky.js';
-
-let container, stats;
-let camera, scene, renderer;
-let controls, water, sun, mesh;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 0.5;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
-    camera.position.set(30, 30, 100);
-
-    //
-
-    sun = new THREE.Vector3();
-
-    // Water
-
-    const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
-
-    water = new Water(waterGeometry, {
-        textureWidth: 512,
-        textureHeight: 512,
-        waterNormals: new THREE.TextureLoader().load('textures/waternormals.jpg', function (texture) {
-            texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
-        }),
-        sunDirection: new THREE.Vector3(),
-        sunColor: 0xffffff,
-        waterColor: 0x001e0f,
-        distortionScale: 3.7,
-        fog: scene.fog !== undefined,
-    });
-
-    water.rotation.x = -Math.PI / 2;
-
-    scene.add(water);
-
-    // Skybox
-
-    const sky = new Sky();
-    sky.scale.setScalar(10000);
-    scene.add(sky);
-
-    const skyUniforms = sky.material.uniforms;
-
-    skyUniforms['turbidity'].value = 10;
-    skyUniforms['rayleigh'].value = 2;
-    skyUniforms['mieCoefficient'].value = 0.005;
-    skyUniforms['mieDirectionalG'].value = 0.8;
-
-    const parameters = {
-        elevation: 2,
-        azimuth: 180,
-    };
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    const sceneEnv = new THREE.Scene();
-
-    let renderTarget;
-
-    function updateSun() {
-        const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
-        const theta = THREE.MathUtils.degToRad(parameters.azimuth);
-
-        sun.setFromSphericalCoords(1, phi, theta);
-
-        sky.material.uniforms['sunPosition'].value.copy(sun);
-        water.material.uniforms['sunDirection'].value.copy(sun).normalize();
-
-        if (renderTarget !== undefined) renderTarget.dispose();
-
-        sceneEnv.add(sky);
-        renderTarget = pmremGenerator.fromScene(sceneEnv);
-        scene.add(sky);
-
-        scene.environment = renderTarget.texture;
-    }
-
-    updateSun();
-
-    //
-
-    const geometry = new THREE.BoxGeometry(30, 30, 30);
-    const material = new THREE.MeshStandardMaterial({ roughness: 0 });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI * 0.495;
-    controls.target.set(0, 10, 0);
-    controls.minDistance = 40.0;
-    controls.maxDistance = 200.0;
-    controls.update();
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // GUI
-
-    const gui = new GUI();
-
-    const folderSky = gui.addFolder('Sky');
-    folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun);
-    folderSky.add(parameters, 'azimuth', -180, 180, 0.1).onChange(updateSun);
-    folderSky.open();
-
-    const waterUniforms = water.material.uniforms;
-
-    const folderWater = gui.addFolder('Water');
-    folderWater.add(waterUniforms.distortionScale, 'value', 0, 8, 0.1).name('distortionScale');
-    folderWater.add(waterUniforms.size, 'value', 0.1, 10, 0.1).name('size');
-    folderWater.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = performance.now() * 0.001;
-
-    mesh.position.y = Math.sin(time) * 20 + 5;
-    mesh.rotation.x = time * 0.5;
-    mesh.rotation.z = time * 0.51;
-
-    water.material.uniforms['time'].value += 1.0 / 60.0;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shaders_sky.ts b/examples-testing/examples/webgl_shaders_sky.ts
deleted file mode 100644
index 18020f78f..000000000
--- a/examples-testing/examples/webgl_shaders_sky.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Sky } from 'three/addons/objects/Sky.js';
-
-let camera, scene, renderer;
-
-let sky, sun;
-
-init();
-render();
-
-function initSky() {
-    // Add Sky
-    sky = new Sky();
-    sky.scale.setScalar(450000);
-    scene.add(sky);
-
-    sun = new THREE.Vector3();
-
-    /// GUI
-
-    const effectController = {
-        turbidity: 10,
-        rayleigh: 3,
-        mieCoefficient: 0.005,
-        mieDirectionalG: 0.7,
-        elevation: 2,
-        azimuth: 180,
-        exposure: renderer.toneMappingExposure,
-    };
-
-    function guiChanged() {
-        const uniforms = sky.material.uniforms;
-        uniforms['turbidity'].value = effectController.turbidity;
-        uniforms['rayleigh'].value = effectController.rayleigh;
-        uniforms['mieCoefficient'].value = effectController.mieCoefficient;
-        uniforms['mieDirectionalG'].value = effectController.mieDirectionalG;
-
-        const phi = THREE.MathUtils.degToRad(90 - effectController.elevation);
-        const theta = THREE.MathUtils.degToRad(effectController.azimuth);
-
-        sun.setFromSphericalCoords(1, phi, theta);
-
-        uniforms['sunPosition'].value.copy(sun);
-
-        renderer.toneMappingExposure = effectController.exposure;
-        renderer.render(scene, camera);
-    }
-
-    const gui = new GUI();
-
-    gui.add(effectController, 'turbidity', 0.0, 20.0, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'rayleigh', 0.0, 4, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'mieCoefficient', 0.0, 0.1, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'mieDirectionalG', 0.0, 1, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'elevation', 0, 90, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'azimuth', -180, 180, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'exposure', 0, 1, 0.0001).onChange(guiChanged);
-
-    guiChanged();
-}
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 2000000);
-    camera.position.set(0, 100, 2000);
-
-    scene = new THREE.Scene();
-
-    const helper = new THREE.GridHelper(10000, 2, 0xffffff, 0xffffff);
-    scene.add(helper);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 0.5;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    //controls.maxPolarAngle = Math.PI / 2;
-    controls.enableZoom = false;
-    controls.enablePan = false;
-
-    initSky();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shadow_contact.ts b/examples-testing/examples/webgl_shadow_contact.ts
deleted file mode 100644
index 9eda35b83..000000000
--- a/examples-testing/examples/webgl_shadow_contact.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';
-import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';
-
-let camera, scene, renderer, stats, gui;
-
-const meshes = [];
-
-const PLANE_WIDTH = 2.5;
-const PLANE_HEIGHT = 2.5;
-const CAMERA_HEIGHT = 0.3;
-
-const state = {
-    shadow: {
-        blur: 3.5,
-        darkness: 1,
-        opacity: 1,
-    },
-    plane: {
-        color: '#ffffff',
-        opacity: 1,
-    },
-    showWireframe: false,
-};
-
-let shadowGroup,
-    renderTarget,
-    renderTargetBlur,
-    shadowCamera,
-    cameraHelper,
-    depthMaterial,
-    horizontalBlurMaterial,
-    verticalBlurMaterial;
-
-let plane, blurPlane, fillPlane;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0.5, 1, 2);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // add the example meshes
-
-    const geometries = [
-        new THREE.BoxGeometry(0.4, 0.4, 0.4),
-        new THREE.IcosahedronGeometry(0.3),
-        new THREE.TorusKnotGeometry(0.4, 0.05, 256, 24, 1, 3),
-    ];
-
-    const material = new THREE.MeshNormalMaterial();
-
-    for (let i = 0, l = geometries.length; i < l; i++) {
-        const angle = (i / l) * Math.PI * 2;
-
-        const geometry = geometries[i];
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.y = 0.1;
-        mesh.position.x = Math.cos(angle) / 2.0;
-        mesh.position.z = Math.sin(angle) / 2.0;
-        scene.add(mesh);
-        meshes.push(mesh);
-    }
-
-    // the container, if you need to move the plane just move this
-    shadowGroup = new THREE.Group();
-    shadowGroup.position.y = -0.3;
-    scene.add(shadowGroup);
-
-    // the render target that will show the shadows in the plane texture
-    renderTarget = new THREE.WebGLRenderTarget(512, 512);
-    renderTarget.texture.generateMipmaps = false;
-
-    // the render target that we will use to blur the first render target
-    renderTargetBlur = new THREE.WebGLRenderTarget(512, 512);
-    renderTargetBlur.texture.generateMipmaps = false;
-
-    // make a plane and make it face up
-    const planeGeometry = new THREE.PlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT).rotateX(Math.PI / 2);
-    const planeMaterial = new THREE.MeshBasicMaterial({
-        map: renderTarget.texture,
-        opacity: state.shadow.opacity,
-        transparent: true,
-        depthWrite: false,
-    });
-    plane = new THREE.Mesh(planeGeometry, planeMaterial);
-    // make sure it's rendered after the fillPlane
-    plane.renderOrder = 1;
-    shadowGroup.add(plane);
-
-    // the y from the texture is flipped!
-    plane.scale.y = -1;
-
-    // the plane onto which to blur the texture
-    blurPlane = new THREE.Mesh(planeGeometry);
-    blurPlane.visible = false;
-    shadowGroup.add(blurPlane);
-
-    // the plane with the color of the ground
-    const fillPlaneMaterial = new THREE.MeshBasicMaterial({
-        color: state.plane.color,
-        opacity: state.plane.opacity,
-        transparent: true,
-        depthWrite: false,
-    });
-    fillPlane = new THREE.Mesh(planeGeometry, fillPlaneMaterial);
-    fillPlane.rotateX(Math.PI);
-    shadowGroup.add(fillPlane);
-
-    // the camera to render the depth material from
-    shadowCamera = new THREE.OrthographicCamera(
-        -PLANE_WIDTH / 2,
-        PLANE_WIDTH / 2,
-        PLANE_HEIGHT / 2,
-        -PLANE_HEIGHT / 2,
-        0,
-        CAMERA_HEIGHT,
-    );
-    shadowCamera.rotation.x = Math.PI / 2; // get the camera to look up
-    shadowGroup.add(shadowCamera);
-
-    cameraHelper = new THREE.CameraHelper(shadowCamera);
-
-    // like MeshDepthMaterial, but goes from black to transparent
-    depthMaterial = new THREE.MeshDepthMaterial();
-    depthMaterial.userData.darkness = { value: state.shadow.darkness };
-    depthMaterial.onBeforeCompile = function (shader) {
-        shader.uniforms.darkness = depthMaterial.userData.darkness;
-        shader.fragmentShader = /* glsl */ `
-						uniform float darkness;
-						${shader.fragmentShader.replace(
-                            'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
-                            'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );',
-                        )}
-					`;
-    };
-
-    depthMaterial.depthTest = false;
-    depthMaterial.depthWrite = false;
-
-    horizontalBlurMaterial = new THREE.ShaderMaterial(HorizontalBlurShader);
-    horizontalBlurMaterial.depthTest = false;
-
-    verticalBlurMaterial = new THREE.ShaderMaterial(VerticalBlurShader);
-    verticalBlurMaterial.depthTest = false;
-
-    //
-
-    gui = new GUI();
-    const shadowFolder = gui.addFolder('shadow');
-    shadowFolder.open();
-    const planeFolder = gui.addFolder('plane');
-    planeFolder.open();
-
-    shadowFolder.add(state.shadow, 'blur', 0, 15, 0.1);
-    shadowFolder.add(state.shadow, 'darkness', 1, 5, 0.1).onChange(function () {
-        depthMaterial.userData.darkness.value = state.shadow.darkness;
-    });
-    shadowFolder.add(state.shadow, 'opacity', 0, 1, 0.01).onChange(function () {
-        plane.material.opacity = state.shadow.opacity;
-    });
-    planeFolder.addColor(state.plane, 'color').onChange(function () {
-        fillPlane.material.color = new THREE.Color(state.plane.color);
-    });
-    planeFolder.add(state.plane, 'opacity', 0, 1, 0.01).onChange(function () {
-        fillPlane.material.opacity = state.plane.opacity;
-    });
-
-    gui.add(state, 'showWireframe').onChange(function () {
-        if (state.showWireframe) {
-            scene.add(cameraHelper);
-        } else {
-            scene.remove(cameraHelper);
-        }
-    });
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    new OrbitControls(camera, renderer.domElement);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-// renderTarget --> blurPlane (horizontalBlur) --> renderTargetBlur --> blurPlane (verticalBlur) --> renderTarget
-function blurShadow(amount) {
-    blurPlane.visible = true;
-
-    // blur horizontally and draw in the renderTargetBlur
-    blurPlane.material = horizontalBlurMaterial;
-    blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture;
-    horizontalBlurMaterial.uniforms.h.value = (amount * 1) / 256;
-
-    renderer.setRenderTarget(renderTargetBlur);
-    renderer.render(blurPlane, shadowCamera);
-
-    // blur vertically and draw in the main renderTarget
-    blurPlane.material = verticalBlurMaterial;
-    blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture;
-    verticalBlurMaterial.uniforms.v.value = (amount * 1) / 256;
-
-    renderer.setRenderTarget(renderTarget);
-    renderer.render(blurPlane, shadowCamera);
-
-    blurPlane.visible = false;
-}
-
-function animate() {
-    meshes.forEach(mesh => {
-        mesh.rotation.x += 0.01;
-        mesh.rotation.y += 0.02;
-    });
-
-    //
-
-    // remove the background
-    const initialBackground = scene.background;
-    scene.background = null;
-
-    // force the depthMaterial to everything
-    cameraHelper.visible = false;
-    scene.overrideMaterial = depthMaterial;
-
-    // set renderer clear alpha
-    const initialClearAlpha = renderer.getClearAlpha();
-    renderer.setClearAlpha(0);
-
-    // render to the render target to get the depths
-    renderer.setRenderTarget(renderTarget);
-    renderer.render(scene, shadowCamera);
-
-    // and reset the override material
-    scene.overrideMaterial = null;
-    cameraHelper.visible = true;
-
-    blurShadow(state.shadow.blur);
-
-    // a second pass to reduce the artifacts
-    // (0.4 is the minimum blur amout so that the artifacts are gone)
-    blurShadow(state.shadow.blur * 0.4);
-
-    // reset and render the normal scene
-    renderer.setRenderTarget(null);
-    renderer.setClearAlpha(initialClearAlpha);
-    scene.background = initialBackground;
-
-    renderer.render(scene, camera);
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_shadowmap.ts b/examples-testing/examples/webgl_shadowmap.ts
deleted file mode 100644
index 6d0ac3adb..000000000
--- a/examples-testing/examples/webgl_shadowmap.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js';
-
-const SHADOW_MAP_WIDTH = 2048,
-    SHADOW_MAP_HEIGHT = 1024;
-
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-const FLOOR = -250;
-
-let camera, controls, scene, renderer;
-let container, stats;
-
-const NEAR = 10,
-    FAR = 3000;
-
-let mixer;
-
-const morphs = [];
-
-let light;
-let lightShadowMapViewer;
-
-const clock = new THREE.Clock();
-
-let showHUD = false;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(23, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR);
-    camera.position.set(700, 50, 1900);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x59472b);
-    scene.fog = new THREE.Fog(0x59472b, 1000, FAR);
-
-    // LIGHTS
-
-    const ambient = new THREE.AmbientLight(0xffffff);
-    scene.add(ambient);
-
-    light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 1500, 1000);
-    light.castShadow = true;
-    light.shadow.camera.top = 2000;
-    light.shadow.camera.bottom = -2000;
-    light.shadow.camera.left = -2000;
-    light.shadow.camera.right = 2000;
-    light.shadow.camera.near = 1200;
-    light.shadow.camera.far = 2500;
-    light.shadow.bias = 0.0001;
-
-    light.shadow.mapSize.width = SHADOW_MAP_WIDTH;
-    light.shadow.mapSize.height = SHADOW_MAP_HEIGHT;
-
-    scene.add(light);
-
-    createHUD();
-    createScene();
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    renderer.autoClear = false;
-
-    //
-
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFShadowMap;
-
-    // CONTROLS
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-
-    controls.lookSpeed = 0.0125;
-    controls.movementSpeed = 500;
-    controls.lookVertical = true;
-
-    controls.lookAt(scene.position);
-
-    // STATS
-
-    stats = new Stats();
-    //container.appendChild( stats.dom );
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-    window.addEventListener('keydown', onKeyDown);
-}
-
-function onWindowResize() {
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    controls.handleResize();
-}
-
-function onKeyDown(event) {
-    switch (event.keyCode) {
-        case 84 /*t*/:
-            showHUD = !showHUD;
-            break;
-    }
-}
-
-function createHUD() {
-    lightShadowMapViewer = new ShadowMapViewer(light);
-    lightShadowMapViewer.position.x = 10;
-    lightShadowMapViewer.position.y = SCREEN_HEIGHT - SHADOW_MAP_HEIGHT / 4 - 10;
-    lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4;
-    lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4;
-    lightShadowMapViewer.update();
-}
-
-function createScene() {
-    // GROUND
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-    const planeMaterial = new THREE.MeshPhongMaterial({ color: 0xffdd99 });
-
-    const ground = new THREE.Mesh(geometry, planeMaterial);
-
-    ground.position.set(0, FLOOR, 0);
-    ground.rotation.x = -Math.PI / 2;
-    ground.scale.set(100, 100, 100);
-
-    ground.castShadow = false;
-    ground.receiveShadow = true;
-
-    scene.add(ground);
-
-    // TEXT
-
-    const loader = new FontLoader();
-    loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
-        const textGeo = new TextGeometry('THREE.JS', {
-            font: font,
-
-            size: 200,
-            depth: 50,
-            curveSegments: 12,
-
-            bevelThickness: 2,
-            bevelSize: 5,
-            bevelEnabled: true,
-        });
-
-        textGeo.computeBoundingBox();
-        const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
-
-        const textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, specular: 0xffffff });
-
-        const mesh = new THREE.Mesh(textGeo, textMaterial);
-        mesh.position.x = centerOffset;
-        mesh.position.y = FLOOR + 67;
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-    });
-
-    // CUBES
-
-    const cubes1 = new THREE.Mesh(new THREE.BoxGeometry(1500, 220, 150), planeMaterial);
-
-    cubes1.position.y = FLOOR - 50;
-    cubes1.position.z = 20;
-
-    cubes1.castShadow = true;
-    cubes1.receiveShadow = true;
-
-    scene.add(cubes1);
-
-    const cubes2 = new THREE.Mesh(new THREE.BoxGeometry(1600, 170, 250), planeMaterial);
-
-    cubes2.position.y = FLOOR - 50;
-    cubes2.position.z = 20;
-
-    cubes2.castShadow = true;
-    cubes2.receiveShadow = true;
-
-    scene.add(cubes2);
-
-    // MORPHS
-
-    mixer = new THREE.AnimationMixer(scene);
-
-    function addMorph(mesh, clip, speed, duration, x, y, z, fudgeColor) {
-        mesh = mesh.clone();
-        mesh.material = mesh.material.clone();
-
-        if (fudgeColor) {
-            mesh.material.color.offsetHSL(0, Math.random() * 0.5 - 0.25, Math.random() * 0.5 - 0.25);
-        }
-
-        mesh.speed = speed;
-
-        mixer
-            .clipAction(clip, mesh)
-            .setDuration(duration)
-            // to shift the playback out of phase:
-            .startAt(-duration * Math.random())
-            .play();
-
-        mesh.position.set(x, y, z);
-        mesh.rotation.y = Math.PI / 2;
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-
-        morphs.push(mesh);
-    }
-
-    const gltfloader = new GLTFLoader();
-
-    gltfloader.load('models/gltf/Horse.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-
-        const clip = gltf.animations[0];
-
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 300, true);
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 450, true);
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, 600, true);
-
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -300, true);
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -450, true);
-        addMorph(mesh, clip, 550, 1, 100 - Math.random() * 1000, FLOOR, -600, true);
-    });
-
-    gltfloader.load('models/gltf/Flamingo.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-        const clip = gltf.animations[0];
-
-        addMorph(mesh, clip, 500, 1, 500 - Math.random() * 500, FLOOR + 350, 40);
-    });
-
-    gltfloader.load('models/gltf/Stork.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-        const clip = gltf.animations[0];
-
-        addMorph(mesh, clip, 350, 1, 500 - Math.random() * 500, FLOOR + 350, 340);
-    });
-
-    gltfloader.load('models/gltf/Parrot.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-        const clip = gltf.animations[0];
-
-        addMorph(mesh, clip, 450, 0.5, 500 - Math.random() * 500, FLOOR + 300, 700);
-    });
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    mixer.update(delta);
-
-    for (let i = 0; i < morphs.length; i++) {
-        const morph = morphs[i];
-
-        morph.position.x += morph.speed * delta;
-
-        if (morph.position.x > 2000) {
-            morph.position.x = -1000 - Math.random() * 500;
-        }
-    }
-
-    controls.update(delta);
-
-    renderer.clear();
-    renderer.render(scene, camera);
-
-    // Render debug HUD with shadow map
-
-    if (showHUD) {
-        lightShadowMapViewer.render(renderer);
-    }
-}
diff --git a/examples-testing/examples/webgl_shadowmap_csm.ts b/examples-testing/examples/webgl_shadowmap_csm.ts
deleted file mode 100644
index c89bc02df..000000000
--- a/examples-testing/examples/webgl_shadowmap_csm.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { CSM } from 'three/addons/csm/CSM.js';
-import { CSMHelper } from 'three/addons/csm/CSMHelper.js';
-
-let renderer, scene, camera, orthoCamera, controls, csm, csmHelper;
-
-const params = {
-    orthographic: false,
-    fade: false,
-    shadows: true,
-    far: 1000,
-    mode: 'practical',
-    lightX: -1,
-    lightY: -1,
-    lightZ: -1,
-    margin: 100,
-    lightFar: 5000,
-    lightNear: 1,
-    autoUpdateHelper: true,
-    updateHelper: function () {
-        csmHelper.update();
-    },
-};
-
-init();
-
-function updateOrthoCamera() {
-    const size = controls.target.distanceTo(camera.position);
-    const aspect = camera.aspect;
-
-    orthoCamera.left = (size * aspect) / -2;
-    orthoCamera.right = (size * aspect) / 2;
-
-    orthoCamera.top = size / 2;
-    orthoCamera.bottom = size / -2;
-    orthoCamera.position.copy(camera.position);
-    orthoCamera.rotation.copy(camera.rotation);
-    orthoCamera.updateProjectionMatrix();
-}
-
-function init() {
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color('#454e61');
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 5000);
-    orthoCamera = new THREE.OrthographicCamera();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-    renderer.shadowMap.enabled = params.shadows;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI / 2;
-    camera.position.set(60, 60, 0);
-    controls.target = new THREE.Vector3(-100, 10, 0);
-    controls.update();
-
-    const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
-    scene.add(ambientLight);
-
-    const additionalDirectionalLight = new THREE.DirectionalLight(0x000020, 1.5);
-    additionalDirectionalLight.position
-        .set(params.lightX, params.lightY, params.lightZ)
-        .normalize()
-        .multiplyScalar(-200);
-    scene.add(additionalDirectionalLight);
-
-    csm = new CSM({
-        maxFar: params.far,
-        cascades: 4,
-        mode: params.mode,
-        parent: scene,
-        shadowMapSize: 1024,
-        lightDirection: new THREE.Vector3(params.lightX, params.lightY, params.lightZ).normalize(),
-        camera: camera,
-    });
-
-    csmHelper = new CSMHelper(csm);
-    csmHelper.visible = false;
-    scene.add(csmHelper);
-
-    const floorMaterial = new THREE.MeshPhongMaterial({ color: '#252a34' });
-    csm.setupMaterial(floorMaterial);
-
-    const floor = new THREE.Mesh(new THREE.PlaneGeometry(10000, 10000, 8, 8), floorMaterial);
-    floor.rotation.x = -Math.PI / 2;
-    floor.castShadow = true;
-    floor.receiveShadow = true;
-    scene.add(floor);
-
-    const material1 = new THREE.MeshPhongMaterial({ color: '#08d9d6' });
-    csm.setupMaterial(material1);
-
-    const material2 = new THREE.MeshPhongMaterial({ color: '#ff2e63' });
-    csm.setupMaterial(material2);
-
-    const geometry = new THREE.BoxGeometry(10, 10, 10);
-
-    for (let i = 0; i < 40; i++) {
-        const cube1 = new THREE.Mesh(geometry, i % 2 === 0 ? material1 : material2);
-        cube1.castShadow = true;
-        cube1.receiveShadow = true;
-        scene.add(cube1);
-        cube1.position.set(-i * 25, 20, 30);
-        cube1.scale.y = Math.random() * 2 + 6;
-
-        const cube2 = new THREE.Mesh(geometry, i % 2 === 0 ? material2 : material1);
-        cube2.castShadow = true;
-        cube2.receiveShadow = true;
-        scene.add(cube2);
-        cube2.position.set(-i * 25, 20, -30);
-        cube2.scale.y = Math.random() * 2 + 6;
-    }
-
-    const gui = new GUI();
-
-    gui.add(params, 'orthographic').onChange(function (value) {
-        csm.camera = value ? orthoCamera : camera;
-        csm.updateFrustums();
-    });
-
-    gui.add(params, 'fade').onChange(function (value) {
-        csm.fade = value;
-        csm.updateFrustums();
-    });
-
-    gui.add(params, 'shadows').onChange(function (value) {
-        renderer.shadowMap.enabled = value;
-
-        scene.traverse(function (child) {
-            if (child.material) {
-                child.material.needsUpdate = true;
-            }
-        });
-    });
-
-    gui.add(params, 'far', 1, 5000)
-        .step(1)
-        .name('shadow far')
-        .onChange(function (value) {
-            csm.maxFar = value;
-            csm.updateFrustums();
-        });
-
-    gui.add(params, 'mode', ['uniform', 'logarithmic', 'practical'])
-        .name('frustum split mode')
-        .onChange(function (value) {
-            csm.mode = value;
-            csm.updateFrustums();
-        });
-
-    gui.add(params, 'lightX', -1, 1)
-        .name('light direction x')
-        .onChange(function (value) {
-            csm.lightDirection.x = value;
-        });
-
-    gui.add(params, 'lightY', -1, 1)
-        .name('light direction y')
-        .onChange(function (value) {
-            csm.lightDirection.y = value;
-        });
-
-    gui.add(params, 'lightZ', -1, 1)
-        .name('light direction z')
-        .onChange(function (value) {
-            csm.lightDirection.z = value;
-        });
-
-    gui.add(params, 'margin', 0, 200)
-        .name('light margin')
-        .onChange(function (value) {
-            csm.lightMargin = value;
-        });
-
-    gui.add(params, 'lightNear', 1, 10000)
-        .name('light near')
-        .onChange(function (value) {
-            for (let i = 0; i < csm.lights.length; i++) {
-                csm.lights[i].shadow.camera.near = value;
-                csm.lights[i].shadow.camera.updateProjectionMatrix();
-            }
-        });
-
-    gui.add(params, 'lightFar', 1, 10000)
-        .name('light far')
-        .onChange(function (value) {
-            for (let i = 0; i < csm.lights.length; i++) {
-                csm.lights[i].shadow.camera.far = value;
-                csm.lights[i].shadow.camera.updateProjectionMatrix();
-            }
-        });
-
-    const helperFolder = gui.addFolder('helper');
-
-    helperFolder.add(csmHelper, 'visible');
-
-    helperFolder.add(csmHelper, 'displayFrustum').onChange(function () {
-        csmHelper.updateVisibility();
-    });
-
-    helperFolder.add(csmHelper, 'displayPlanes').onChange(function () {
-        csmHelper.updateVisibility();
-    });
-
-    helperFolder.add(csmHelper, 'displayShadowBounds').onChange(function () {
-        csmHelper.updateVisibility();
-    });
-
-    helperFolder.add(params, 'autoUpdateHelper').name('auto update');
-
-    helperFolder.add(params, 'updateHelper').name('update');
-
-    helperFolder.open();
-
-    window.addEventListener('resize', function () {
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-
-        updateOrthoCamera();
-        csm.updateFrustums();
-
-        renderer.setSize(window.innerWidth, window.innerHeight);
-    });
-}
-
-function animate() {
-    camera.updateMatrixWorld();
-    csm.update();
-    controls.update();
-
-    if (params.orthographic) {
-        updateOrthoCamera();
-        csm.updateFrustums();
-
-        if (params.autoUpdateHelper) {
-            csmHelper.update();
-        }
-
-        renderer.render(scene, orthoCamera);
-    } else {
-        if (params.autoUpdateHelper) {
-            csmHelper.update();
-        }
-
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgl_shadowmap_pcss.ts b/examples-testing/examples/webgl_shadowmap_pcss.ts
deleted file mode 100644
index a47a011ff..000000000
--- a/examples-testing/examples/webgl_shadowmap_pcss.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let stats;
-let camera, scene, renderer;
-
-let group;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0xcce0ff, 5, 100);
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
-
-    // We use this particular camera position in order to expose a bug that can sometimes happen presumably
-    // due to lack of precision when interpolating values over really large triangles.
-    // It reproduced on at least NVIDIA GTX 1080 and GTX 1050 Ti GPUs when the ground plane was not
-    // subdivided into segments.
-    camera.position.x = 7;
-    camera.position.y = 13;
-    camera.position.z = 7;
-
-    scene.add(camera);
-
-    // lights
-
-    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
-
-    const light = new THREE.DirectionalLight(0xf0f6ff, 4.5);
-    light.position.set(2, 8, 4);
-
-    light.castShadow = true;
-    light.shadow.mapSize.width = 1024;
-    light.shadow.mapSize.height = 1024;
-    light.shadow.camera.far = 20;
-
-    scene.add(light);
-
-    // scene.add( new DirectionalLightHelper( light ) );
-    scene.add(new THREE.CameraHelper(light.shadow.camera));
-
-    // group
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const geometry = new THREE.SphereGeometry(0.3, 20, 20);
-
-    for (let i = 0; i < 20; i++) {
-        const material = new THREE.MeshPhongMaterial({ color: Math.random() * 0xffffff });
-
-        const sphere = new THREE.Mesh(geometry, material);
-        sphere.position.x = Math.random() - 0.5;
-        sphere.position.z = Math.random() - 0.5;
-        sphere.position.normalize();
-        sphere.position.multiplyScalar(Math.random() * 2 + 1);
-        sphere.castShadow = true;
-        sphere.receiveShadow = true;
-        sphere.userData.phase = Math.random() * Math.PI;
-        group.add(sphere);
-    }
-
-    // ground
-
-    const groundMaterial = new THREE.MeshPhongMaterial({ color: 0x898989 });
-
-    const ground = new THREE.Mesh(new THREE.PlaneGeometry(20000, 20000, 8, 8), groundMaterial);
-    ground.rotation.x = -Math.PI / 2;
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // column
-
-    const column = new THREE.Mesh(new THREE.BoxGeometry(1, 4, 1), groundMaterial);
-    column.position.y = 2;
-    column.castShadow = true;
-    column.receiveShadow = true;
-    scene.add(column);
-
-    // overwrite shadowmap code
-
-    let shader = THREE.ShaderChunk.shadowmap_pars_fragment;
-
-    shader = shader.replace(
-        '#ifdef USE_SHADOWMAP',
-        '#ifdef USE_SHADOWMAP' + document.getElementById('PCSS').textContent,
-    );
-
-    shader = shader.replace(
-        '#if defined( SHADOWMAP_TYPE_PCF )',
-        document.getElementById('PCSSGetShadow').textContent + '#if defined( SHADOWMAP_TYPE_PCF )',
-    );
-
-    THREE.ShaderChunk.shadowmap_pars_fragment = shader;
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.setClearColor(scene.fog.color);
-
-    container.appendChild(renderer.domElement);
-
-    renderer.shadowMap.enabled = true;
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI * 0.5;
-    controls.minDistance = 10;
-    controls.maxDistance = 75;
-    controls.target.set(0, 2.5, 0);
-    controls.update();
-
-    // performance monitor
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = performance.now() / 1000;
-
-    group.traverse(function (child) {
-        if ('phase' in child.userData) {
-            child.position.y = Math.abs(Math.sin(time + child.userData.phase)) * 4 + 0.3;
-        }
-    });
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_shadowmap_performance.ts b/examples-testing/examples/webgl_shadowmap_performance.ts
deleted file mode 100644
index 0e45b63f9..000000000
--- a/examples-testing/examples/webgl_shadowmap_performance.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-const SHADOW_MAP_WIDTH = 2048,
-    SHADOW_MAP_HEIGHT = 1024;
-
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-const FLOOR = -250;
-
-const ANIMATION_GROUPS = 25;
-
-let camera, controls, scene, renderer;
-let stats;
-
-const NEAR = 5,
-    FAR = 3000;
-
-let morph, mixer;
-
-const morphs = [],
-    animGroups = [];
-
-const clock = new THREE.Clock();
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(23, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR);
-    camera.position.set(700, 50, 1900);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x59472b);
-    scene.fog = new THREE.Fog(0x59472b, 1000, FAR);
-
-    // LIGHTS
-
-    const ambient = new THREE.AmbientLight(0xffffff);
-    scene.add(ambient);
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 1500, 1000);
-    light.castShadow = true;
-    light.shadow.camera.top = 2000;
-    light.shadow.camera.bottom = -2000;
-    light.shadow.camera.left = -2000;
-    light.shadow.camera.right = 2000;
-    light.shadow.camera.near = 1200;
-    light.shadow.camera.far = 2500;
-    light.shadow.bias = 0.0001;
-
-    light.shadow.mapSize.width = SHADOW_MAP_WIDTH;
-    light.shadow.mapSize.height = SHADOW_MAP_HEIGHT;
-
-    scene.add(light);
-
-    createScene();
-
-    // RENDERER
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    renderer.autoClear = false;
-
-    //
-
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-    // CONTROLS
-
-    controls = new FirstPersonControls(camera, renderer.domElement);
-
-    controls.lookSpeed = 0.0125;
-    controls.movementSpeed = 500;
-    controls.lookVertical = true;
-
-    controls.lookAt(scene.position);
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    controls.handleResize();
-}
-
-function createScene() {
-    // GROUND
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-    const planeMaterial = new THREE.MeshPhongMaterial({ color: 0xffdd99 });
-
-    const ground = new THREE.Mesh(geometry, planeMaterial);
-
-    ground.position.set(0, FLOOR, 0);
-    ground.rotation.x = -Math.PI / 2;
-    ground.scale.set(100, 100, 100);
-
-    ground.castShadow = false;
-    ground.receiveShadow = true;
-
-    scene.add(ground);
-
-    // TEXT
-
-    const loader = new FontLoader();
-    loader.load('fonts/helvetiker_bold.typeface.json', function (font) {
-        const textGeo = new TextGeometry('THREE.JS', {
-            font: font,
-
-            size: 200,
-            depth: 50,
-            curveSegments: 12,
-
-            bevelThickness: 2,
-            bevelSize: 5,
-            bevelEnabled: true,
-        });
-
-        textGeo.computeBoundingBox();
-        const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
-
-        const textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, specular: 0xffffff });
-
-        const mesh = new THREE.Mesh(textGeo, textMaterial);
-        mesh.position.x = centerOffset;
-        mesh.position.y = FLOOR + 67;
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-    });
-
-    // CUBES
-
-    const cubes1 = new THREE.Mesh(new THREE.BoxGeometry(1500, 220, 150), planeMaterial);
-
-    cubes1.position.y = FLOOR - 50;
-    cubes1.position.z = 20;
-
-    cubes1.castShadow = true;
-    cubes1.receiveShadow = true;
-
-    scene.add(cubes1);
-
-    const cubes2 = new THREE.Mesh(new THREE.BoxGeometry(1600, 170, 250), planeMaterial);
-
-    cubes2.position.y = FLOOR - 50;
-    cubes2.position.z = 20;
-
-    cubes2.castShadow = true;
-    cubes2.receiveShadow = true;
-
-    scene.add(cubes2);
-
-    mixer = new THREE.AnimationMixer(scene);
-
-    for (let i = 0; i !== ANIMATION_GROUPS; ++i) {
-        const group = new THREE.AnimationObjectGroup();
-        animGroups.push(group);
-    }
-
-    // MORPHS
-
-    function addMorph(mesh, clip, speed, duration, x, y, z, fudgeColor, massOptimization) {
-        mesh = mesh.clone();
-        mesh.material = mesh.material.clone();
-
-        if (fudgeColor) {
-            mesh.material.color.offsetHSL(0, Math.random() * 0.5 - 0.25, Math.random() * 0.5 - 0.25);
-        }
-
-        mesh.speed = speed;
-
-        if (massOptimization) {
-            const index = Math.floor(Math.random() * ANIMATION_GROUPS),
-                animGroup = animGroups[index];
-
-            animGroup.add(mesh);
-
-            if (!mixer.existingAction(clip, animGroup)) {
-                const randomness = 0.6 * Math.random() - 0.3;
-                const phase = (index + randomness) / ANIMATION_GROUPS;
-
-                mixer
-                    .clipAction(clip, animGroup)
-                    .setDuration(duration)
-                    .startAt(-duration * phase)
-                    .play();
-            }
-        } else {
-            mixer
-                .clipAction(clip, mesh)
-                .setDuration(duration)
-                .startAt(-duration * Math.random())
-                .play();
-        }
-
-        mesh.position.set(x, y, z);
-        mesh.rotation.y = Math.PI / 2;
-
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-
-        scene.add(mesh);
-
-        morphs.push(mesh);
-    }
-
-    const gltfLoader = new GLTFLoader();
-    gltfLoader.load('models/gltf/Horse.glb', function (gltf) {
-        const mesh = gltf.scene.children[0];
-        const clip = gltf.animations[0];
-
-        for (let i = -600; i < 601; i += 2) {
-            addMorph(mesh, clip, 550, 1, 100 - Math.random() * 3000, FLOOR, i, true, true);
-        }
-    });
-}
-
-//
-
-function animate() {
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    if (mixer) mixer.update(delta);
-
-    for (let i = 0; i < morphs.length; i++) {
-        morph = morphs[i];
-
-        morph.position.x += morph.speed * delta;
-
-        if (morph.position.x > 2000) {
-            morph.position.x = -1000 - Math.random() * 500;
-        }
-    }
-
-    controls.update(delta);
-
-    renderer.clear();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shadowmap_pointlight.ts b/examples-testing/examples/webgl_shadowmap_pointlight.ts
deleted file mode 100644
index c68d69749..000000000
--- a/examples-testing/examples/webgl_shadowmap_pointlight.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, stats;
-let pointLight, pointLight2;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 10, 40);
-
-    scene = new THREE.Scene();
-    scene.add(new THREE.AmbientLight(0x111122, 3));
-
-    // lights
-
-    function createLight(color) {
-        const intensity = 200;
-
-        const light = new THREE.PointLight(color, intensity, 20);
-        light.castShadow = true;
-        light.shadow.bias = -0.005; // reduces self-shadowing on double-sided objects
-
-        let geometry = new THREE.SphereGeometry(0.3, 12, 6);
-        let material = new THREE.MeshBasicMaterial({ color: color });
-        material.color.multiplyScalar(intensity);
-        let sphere = new THREE.Mesh(geometry, material);
-        light.add(sphere);
-
-        const texture = new THREE.CanvasTexture(generateTexture());
-        texture.magFilter = THREE.NearestFilter;
-        texture.wrapT = THREE.RepeatWrapping;
-        texture.wrapS = THREE.RepeatWrapping;
-        texture.repeat.set(1, 4.5);
-
-        geometry = new THREE.SphereGeometry(2, 32, 8);
-        material = new THREE.MeshPhongMaterial({
-            side: THREE.DoubleSide,
-            alphaMap: texture,
-            alphaTest: 0.5,
-        });
-
-        sphere = new THREE.Mesh(geometry, material);
-        sphere.castShadow = true;
-        sphere.receiveShadow = true;
-        light.add(sphere);
-
-        return light;
-    }
-
-    pointLight = createLight(0x0088ff);
-    scene.add(pointLight);
-
-    pointLight2 = createLight(0xff8888);
-    scene.add(pointLight2);
-    //
-
-    const geometry = new THREE.BoxGeometry(30, 30, 30);
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xa0adaf,
-        shininess: 10,
-        specular: 0x111111,
-        side: THREE.BackSide,
-    });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    mesh.position.y = 10;
-    mesh.receiveShadow = true;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.BasicShadowMap;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 10, 0);
-    controls.update();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function generateTexture() {
-    const canvas = document.createElement('canvas');
-    canvas.width = 2;
-    canvas.height = 2;
-
-    const context = canvas.getContext('2d');
-    context.fillStyle = 'white';
-    context.fillRect(0, 1, 2, 1);
-
-    return canvas;
-}
-
-function animate() {
-    let time = performance.now() * 0.001;
-
-    pointLight.position.x = Math.sin(time * 0.6) * 9;
-    pointLight.position.y = Math.sin(time * 0.7) * 9 + 6;
-    pointLight.position.z = Math.sin(time * 0.8) * 9;
-
-    pointLight.rotation.x = time;
-    pointLight.rotation.z = time;
-
-    time += 10000;
-
-    pointLight2.position.x = Math.sin(time * 0.6) * 9;
-    pointLight2.position.y = Math.sin(time * 0.7) * 9 + 6;
-    pointLight2.position.z = Math.sin(time * 0.8) * 9;
-
-    pointLight2.rotation.x = time;
-    pointLight2.rotation.z = time;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_shadowmap_progressive.ts b/examples-testing/examples/webgl_shadowmap_progressive.ts
deleted file mode 100644
index 86ec68172..000000000
--- a/examples-testing/examples/webgl_shadowmap_progressive.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import * as THREE from 'three';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { TransformControls } from 'three/addons/controls/TransformControls.js';
-import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMap.js';
-
-// ShadowMap + LightMap Res and Number of Directional Lights
-const shadowMapRes = 512,
-    lightMapRes = 1024,
-    lightCount = 8;
-let camera,
-    scene,
-    renderer,
-    controls,
-    control,
-    control2,
-    object = new THREE.Mesh(),
-    lightOrigin = null,
-    progressiveSurfacemap;
-const dirLights = [],
-    lightmapObjects = [];
-const params = {
-    Enable: true,
-    'Blur Edges': true,
-    'Blend Window': 200,
-    'Light Radius': 50,
-    'Ambient Weight': 0.5,
-    'Debug Lightmap': false,
-};
-init();
-createGUI();
-
-function init() {
-    // renderer
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    document.body.appendChild(renderer.domElement);
-
-    // camera
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 100, 200);
-    camera.name = 'Camera';
-
-    // scene
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x949494);
-    scene.fog = new THREE.Fog(0x949494, 1000, 3000);
-
-    // progressive lightmap
-    progressiveSurfacemap = new ProgressiveLightMap(renderer, lightMapRes);
-
-    // directional lighting "origin"
-    lightOrigin = new THREE.Group();
-    lightOrigin.position.set(60, 150, 100);
-    scene.add(lightOrigin);
-
-    // transform gizmo
-    control = new TransformControls(camera, renderer.domElement);
-    control.addEventListener('dragging-changed', event => {
-        controls.enabled = !event.value;
-    });
-    control.attach(lightOrigin);
-    scene.add(control);
-
-    // create 8 directional lights to speed up the convergence
-    for (let l = 0; l < lightCount; l++) {
-        const dirLight = new THREE.DirectionalLight(0xffffff, 1.0 / lightCount);
-        dirLight.name = 'Dir. Light ' + l;
-        dirLight.position.set(200, 200, 200);
-        dirLight.castShadow = true;
-        dirLight.shadow.camera.near = 100;
-        dirLight.shadow.camera.far = 5000;
-        dirLight.shadow.camera.right = 150;
-        dirLight.shadow.camera.left = -150;
-        dirLight.shadow.camera.top = 150;
-        dirLight.shadow.camera.bottom = -150;
-        dirLight.shadow.mapSize.width = shadowMapRes;
-        dirLight.shadow.mapSize.height = shadowMapRes;
-        lightmapObjects.push(dirLight);
-        dirLights.push(dirLight);
-    }
-
-    // ground
-    const groundMesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(600, 600),
-        new THREE.MeshPhongMaterial({ color: 0xffffff, depthWrite: true }),
-    );
-    groundMesh.position.y = -0.1;
-    groundMesh.rotation.x = -Math.PI / 2;
-    groundMesh.name = 'Ground Mesh';
-    lightmapObjects.push(groundMesh);
-    scene.add(groundMesh);
-
-    // model
-    function loadModel() {
-        object.traverse(function (child) {
-            if (child.isMesh) {
-                child.name = 'Loaded Mesh';
-                child.castShadow = true;
-                child.receiveShadow = true;
-                child.material = new THREE.MeshPhongMaterial();
-
-                // This adds the model to the lightmap
-                lightmapObjects.push(child);
-                progressiveSurfacemap.addObjectsToLightMap(lightmapObjects);
-            } else {
-                child.layers.disableAll(); // Disable Rendering for this
-            }
-        });
-        scene.add(object);
-        object.scale.set(2, 2, 2);
-        object.position.set(0, -16, 0);
-        control2 = new TransformControls(camera, renderer.domElement);
-        control2.addEventListener('dragging-changed', event => {
-            controls.enabled = !event.value;
-        });
-        control2.attach(object);
-        scene.add(control2);
-        const lightTarget = new THREE.Group();
-        lightTarget.position.set(0, 20, 0);
-        for (let l = 0; l < dirLights.length; l++) {
-            dirLights[l].target = lightTarget;
-        }
-
-        object.add(lightTarget);
-    }
-
-    const manager = new THREE.LoadingManager(loadModel);
-    const loader = new GLTFLoader(manager);
-    loader.load('models/gltf/ShadowmappableMesh.glb', function (obj) {
-        object = obj.scene.children[0];
-    });
-
-    // controls
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
-    controls.dampingFactor = 0.05;
-    controls.screenSpacePanning = true;
-    controls.minDistance = 100;
-    controls.maxDistance = 500;
-    controls.maxPolarAngle = Math.PI / 1.5;
-    controls.target.set(0, 100, 0);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createGUI() {
-    const gui = new GUI({ title: 'Accumulation Settings' });
-    gui.add(params, 'Enable');
-    gui.add(params, 'Blur Edges');
-    gui.add(params, 'Blend Window', 1, 500).step(1);
-    gui.add(params, 'Light Radius', 0, 200).step(10);
-    gui.add(params, 'Ambient Weight', 0, 1).step(0.1);
-    gui.add(params, 'Debug Lightmap');
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    // Update the inertia on the orbit controls
-    controls.update();
-
-    // Accumulate Surface Maps
-    if (params['Enable']) {
-        progressiveSurfacemap.update(camera, params['Blend Window'], params['Blur Edges']);
-
-        if (!progressiveSurfacemap.firstUpdate) {
-            progressiveSurfacemap.showDebugLightmap(params['Debug Lightmap']);
-        }
-    }
-
-    // Manually Update the Directional Lights
-    for (let l = 0; l < dirLights.length; l++) {
-        // Sometimes they will be sampled from the target direction
-        // Sometimes they will be uniformly sampled from the upper hemisphere
-        if (Math.random() > params['Ambient Weight']) {
-            dirLights[l].position.set(
-                lightOrigin.position.x + Math.random() * params['Light Radius'],
-                lightOrigin.position.y + Math.random() * params['Light Radius'],
-                lightOrigin.position.z + Math.random() * params['Light Radius'],
-            );
-        } else {
-            // Uniform Hemispherical Surface Distribution for Ambient Occlusion
-            const lambda = Math.acos(2 * Math.random() - 1) - 3.14159 / 2.0;
-            const phi = 2 * 3.14159 * Math.random();
-            dirLights[l].position.set(
-                Math.cos(lambda) * Math.cos(phi) * 300 + object.position.x,
-                Math.abs(Math.cos(lambda) * Math.sin(phi) * 300) + object.position.y + 20,
-                Math.sin(lambda) * 300 + object.position.z,
-            );
-        }
-    }
-
-    // Render Scene
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_shadowmap_viewer.ts b/examples-testing/examples/webgl_shadowmap_viewer.ts
deleted file mode 100644
index f974ef038..000000000
--- a/examples-testing/examples/webgl_shadowmap_viewer.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js';
-
-let camera, scene, renderer, clock, stats;
-let dirLight, spotLight;
-let torusKnot, cube;
-let dirLightShadowMapViewer, spotLightShadowMapViewer;
-
-init();
-
-function init() {
-    initScene();
-    initShadowMapViewers();
-    initMisc();
-
-    document.body.appendChild(renderer.domElement);
-    window.addEventListener('resize', onWindowResize);
-}
-
-function initScene() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 15, 35);
-
-    scene = new THREE.Scene();
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0x404040, 3));
-
-    spotLight = new THREE.SpotLight(0xffffff, 500);
-    spotLight.name = 'Spot Light';
-    spotLight.angle = Math.PI / 5;
-    spotLight.penumbra = 0.3;
-    spotLight.position.set(10, 10, 5);
-    spotLight.castShadow = true;
-    spotLight.shadow.camera.near = 8;
-    spotLight.shadow.camera.far = 30;
-    spotLight.shadow.mapSize.width = 1024;
-    spotLight.shadow.mapSize.height = 1024;
-    scene.add(spotLight);
-
-    scene.add(new THREE.CameraHelper(spotLight.shadow.camera));
-
-    dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.name = 'Dir. Light';
-    dirLight.position.set(0, 10, 0);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.near = 1;
-    dirLight.shadow.camera.far = 10;
-    dirLight.shadow.camera.right = 15;
-    dirLight.shadow.camera.left = -15;
-    dirLight.shadow.camera.top = 15;
-    dirLight.shadow.camera.bottom = -15;
-    dirLight.shadow.mapSize.width = 1024;
-    dirLight.shadow.mapSize.height = 1024;
-    scene.add(dirLight);
-
-    scene.add(new THREE.CameraHelper(dirLight.shadow.camera));
-
-    // Geometry
-    let geometry = new THREE.TorusKnotGeometry(25, 8, 75, 20);
-    let material = new THREE.MeshPhongMaterial({
-        color: 0xff0000,
-        shininess: 150,
-        specular: 0x222222,
-    });
-
-    torusKnot = new THREE.Mesh(geometry, material);
-    torusKnot.scale.multiplyScalar(1 / 18);
-    torusKnot.position.y = 3;
-    torusKnot.castShadow = true;
-    torusKnot.receiveShadow = true;
-    scene.add(torusKnot);
-
-    geometry = new THREE.BoxGeometry(3, 3, 3);
-    cube = new THREE.Mesh(geometry, material);
-    cube.position.set(8, 3, 8);
-    cube.castShadow = true;
-    cube.receiveShadow = true;
-    scene.add(cube);
-
-    geometry = new THREE.BoxGeometry(10, 0.15, 10);
-    material = new THREE.MeshPhongMaterial({
-        color: 0xa0adaf,
-        shininess: 150,
-        specular: 0x111111,
-    });
-
-    const ground = new THREE.Mesh(geometry, material);
-    ground.scale.multiplyScalar(3);
-    ground.castShadow = false;
-    ground.receiveShadow = true;
-    scene.add(ground);
-}
-
-function initShadowMapViewers() {
-    dirLightShadowMapViewer = new ShadowMapViewer(dirLight);
-    spotLightShadowMapViewer = new ShadowMapViewer(spotLight);
-    resizeShadowMapViewers();
-}
-
-function initMisc() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.BasicShadowMap;
-
-    // Mouse control
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 2, 0);
-    controls.update();
-
-    clock = new THREE.Clock();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-}
-
-function resizeShadowMapViewers() {
-    const size = window.innerWidth * 0.15;
-
-    dirLightShadowMapViewer.position.x = 10;
-    dirLightShadowMapViewer.position.y = 10;
-    dirLightShadowMapViewer.size.width = size;
-    dirLightShadowMapViewer.size.height = size;
-    dirLightShadowMapViewer.update(); //Required when setting position or size directly
-
-    spotLightShadowMapViewer.size.set(size, size);
-    spotLightShadowMapViewer.position.set(size + 20, 10);
-    // spotLightShadowMapViewer.update();	//NOT required because .set updates automatically
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    resizeShadowMapViewers();
-    dirLightShadowMapViewer.updateForWindowResize();
-    spotLightShadowMapViewer.updateForWindowResize();
-}
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function renderScene() {
-    renderer.render(scene, camera);
-}
-
-function renderShadowMapViewers() {
-    dirLightShadowMapViewer.render(renderer);
-    spotLightShadowMapViewer.render(renderer);
-}
-
-function render() {
-    const delta = clock.getDelta();
-
-    renderScene();
-    renderShadowMapViewers();
-
-    torusKnot.rotation.x += 0.25 * delta;
-    torusKnot.rotation.y += 2 * delta;
-    torusKnot.rotation.z += 1 * delta;
-
-    cube.rotation.x += 0.25 * delta;
-    cube.rotation.y += 2 * delta;
-    cube.rotation.z += 1 * delta;
-}
diff --git a/examples-testing/examples/webgl_shadowmap_vsm.ts b/examples-testing/examples/webgl_shadowmap_vsm.ts
deleted file mode 100644
index 4867c7315..000000000
--- a/examples-testing/examples/webgl_shadowmap_vsm.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, clock, stats;
-let dirLight, spotLight;
-let torusKnot, dirGroup;
-
-init();
-
-function init() {
-    initScene();
-    initMisc();
-
-    // Init gui
-    const gui = new GUI();
-
-    const config = {
-        spotlightRadius: 4,
-        spotlightSamples: 8,
-        dirlightRadius: 4,
-        dirlightSamples: 8,
-    };
-
-    const spotlightFolder = gui.addFolder('Spotlight');
-    spotlightFolder
-        .add(config, 'spotlightRadius')
-        .name('radius')
-        .min(0)
-        .max(25)
-        .onChange(function (value) {
-            spotLight.shadow.radius = value;
-        });
-
-    spotlightFolder
-        .add(config, 'spotlightSamples', 1, 25, 1)
-        .name('samples')
-        .onChange(function (value) {
-            spotLight.shadow.blurSamples = value;
-        });
-    spotlightFolder.open();
-
-    const dirlightFolder = gui.addFolder('Directional Light');
-    dirlightFolder
-        .add(config, 'dirlightRadius')
-        .name('radius')
-        .min(0)
-        .max(25)
-        .onChange(function (value) {
-            dirLight.shadow.radius = value;
-        });
-
-    dirlightFolder
-        .add(config, 'dirlightSamples', 1, 25, 1)
-        .name('samples')
-        .onChange(function (value) {
-            dirLight.shadow.blurSamples = value;
-        });
-    dirlightFolder.open();
-
-    document.body.appendChild(renderer.domElement);
-    window.addEventListener('resize', onWindowResize);
-}
-
-function initScene() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 10, 30);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x222244);
-    scene.fog = new THREE.Fog(0x222244, 50, 100);
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0x444444));
-
-    spotLight = new THREE.SpotLight(0xff8888, 400);
-    spotLight.angle = Math.PI / 5;
-    spotLight.penumbra = 0.3;
-    spotLight.position.set(8, 10, 5);
-    spotLight.castShadow = true;
-    spotLight.shadow.camera.near = 8;
-    spotLight.shadow.camera.far = 200;
-    spotLight.shadow.mapSize.width = 256;
-    spotLight.shadow.mapSize.height = 256;
-    spotLight.shadow.bias = -0.002;
-    spotLight.shadow.radius = 4;
-    scene.add(spotLight);
-
-    dirLight = new THREE.DirectionalLight(0x8888ff, 3);
-    dirLight.position.set(3, 12, 17);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.near = 0.1;
-    dirLight.shadow.camera.far = 500;
-    dirLight.shadow.camera.right = 17;
-    dirLight.shadow.camera.left = -17;
-    dirLight.shadow.camera.top = 17;
-    dirLight.shadow.camera.bottom = -17;
-    dirLight.shadow.mapSize.width = 512;
-    dirLight.shadow.mapSize.height = 512;
-    dirLight.shadow.radius = 4;
-    dirLight.shadow.bias = -0.0005;
-
-    dirGroup = new THREE.Group();
-    dirGroup.add(dirLight);
-    scene.add(dirGroup);
-
-    // Geometry
-
-    const geometry = new THREE.TorusKnotGeometry(25, 8, 75, 20);
-    const material = new THREE.MeshPhongMaterial({
-        color: 0x999999,
-        shininess: 0,
-        specular: 0x222222,
-    });
-
-    torusKnot = new THREE.Mesh(geometry, material);
-    torusKnot.scale.multiplyScalar(1 / 18);
-    torusKnot.position.y = 3;
-    torusKnot.castShadow = true;
-    torusKnot.receiveShadow = true;
-    scene.add(torusKnot);
-
-    const cylinderGeometry = new THREE.CylinderGeometry(0.75, 0.75, 7, 32);
-
-    const pillar1 = new THREE.Mesh(cylinderGeometry, material);
-    pillar1.position.set(8, 3.5, 8);
-    pillar1.castShadow = true;
-    pillar1.receiveShadow = true;
-
-    const pillar2 = pillar1.clone();
-    pillar2.position.set(8, 3.5, -8);
-    const pillar3 = pillar1.clone();
-    pillar3.position.set(-8, 3.5, 8);
-    const pillar4 = pillar1.clone();
-    pillar4.position.set(-8, 3.5, -8);
-
-    scene.add(pillar1);
-    scene.add(pillar2);
-    scene.add(pillar3);
-    scene.add(pillar4);
-
-    const planeGeometry = new THREE.PlaneGeometry(200, 200);
-    const planeMaterial = new THREE.MeshPhongMaterial({
-        color: 0x999999,
-        shininess: 0,
-        specular: 0x111111,
-    });
-
-    const ground = new THREE.Mesh(planeGeometry, planeMaterial);
-    ground.rotation.x = -Math.PI / 2;
-    ground.scale.multiplyScalar(3);
-    ground.castShadow = true;
-    ground.receiveShadow = true;
-    scene.add(ground);
-}
-
-function initMisc() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.VSMShadowMap;
-
-    // Mouse control
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 2, 0);
-    controls.update();
-
-    clock = new THREE.Clock();
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate(time) {
-    const delta = clock.getDelta();
-
-    torusKnot.rotation.x += 0.25 * delta;
-    torusKnot.rotation.y += 0.5 * delta;
-    torusKnot.rotation.z += 1 * delta;
-
-    dirGroup.rotation.y += 0.7 * delta;
-    dirLight.position.z = 17 + Math.sin(time * 0.001) * 5;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_shadowmesh.ts b/examples-testing/examples/webgl_shadowmesh.ts
deleted file mode 100644
index 412fc0283..000000000
--- a/examples-testing/examples/webgl_shadowmesh.ts
+++ /dev/null
@@ -1,250 +0,0 @@
-import * as THREE from 'three';
-
-import { ShadowMesh } from 'three/addons/objects/ShadowMesh.js';
-
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-
-const scene = new THREE.Scene();
-const camera = new THREE.PerspectiveCamera(55, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 3000);
-const clock = new THREE.Clock();
-const renderer = new THREE.WebGLRenderer({ stencil: true });
-
-const sunLight = new THREE.DirectionalLight('rgb(255,255,255)', 3);
-let useDirectionalLight = true;
-let arrowHelper1, arrowHelper2, arrowHelper3;
-const arrowDirection = new THREE.Vector3();
-const arrowPosition1 = new THREE.Vector3();
-const arrowPosition2 = new THREE.Vector3();
-const arrowPosition3 = new THREE.Vector3();
-let groundMesh;
-let lightSphere, lightHolder;
-let pyramid, pyramidShadow;
-let sphere, sphereShadow;
-let cube, cubeShadow;
-let cylinder, cylinderShadow;
-let torus, torusShadow;
-const normalVector = new THREE.Vector3(0, 1, 0);
-const planeConstant = 0.01; // this value must be slightly higher than the groundMesh's y position of 0.0
-const groundPlane = new THREE.Plane(normalVector, planeConstant);
-const lightPosition4D = new THREE.Vector4();
-let verticalAngle = 0;
-let horizontalAngle = 0;
-let frameTime = 0;
-const TWO_PI = Math.PI * 2;
-
-init();
-
-function init() {
-    scene.background = new THREE.Color(0x0096ff);
-
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    document.getElementById('container').appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    camera.position.set(0, 2.5, 10);
-    scene.add(camera);
-    onWindowResize();
-
-    sunLight.position.set(5, 7, -1);
-    sunLight.lookAt(scene.position);
-    scene.add(sunLight);
-
-    lightPosition4D.x = sunLight.position.x;
-    lightPosition4D.y = sunLight.position.y;
-    lightPosition4D.z = sunLight.position.z;
-    // amount of light-ray divergence. Ranging from:
-    // 0.001 = sunlight(min divergence) to 1.0 = pointlight(max divergence)
-    lightPosition4D.w = 0.001; // must be slightly greater than 0, due to 0 causing matrixInverse errors
-
-    // YELLOW ARROW HELPERS
-    arrowDirection.subVectors(scene.position, sunLight.position).normalize();
-
-    arrowPosition1.copy(sunLight.position);
-    arrowHelper1 = new THREE.ArrowHelper(arrowDirection, arrowPosition1, 0.9, 0xffff00, 0.25, 0.08);
-    scene.add(arrowHelper1);
-
-    arrowPosition2.copy(sunLight.position).add(new THREE.Vector3(0, 0.2, 0));
-    arrowHelper2 = new THREE.ArrowHelper(arrowDirection, arrowPosition2, 0.9, 0xffff00, 0.25, 0.08);
-    scene.add(arrowHelper2);
-
-    arrowPosition3.copy(sunLight.position).add(new THREE.Vector3(0, -0.2, 0));
-    arrowHelper3 = new THREE.ArrowHelper(arrowDirection, arrowPosition3, 0.9, 0xffff00, 0.25, 0.08);
-    scene.add(arrowHelper3);
-
-    // LIGHTBULB
-    const lightSphereGeometry = new THREE.SphereGeometry(0.09);
-    const lightSphereMaterial = new THREE.MeshBasicMaterial({ color: 'rgb(255,255,255)' });
-    lightSphere = new THREE.Mesh(lightSphereGeometry, lightSphereMaterial);
-    scene.add(lightSphere);
-    lightSphere.visible = false;
-
-    const lightHolderGeometry = new THREE.CylinderGeometry(0.05, 0.05, 0.13);
-    const lightHolderMaterial = new THREE.MeshBasicMaterial({ color: 'rgb(75,75,75)' });
-    lightHolder = new THREE.Mesh(lightHolderGeometry, lightHolderMaterial);
-    scene.add(lightHolder);
-    lightHolder.visible = false;
-
-    // GROUND
-    const groundGeometry = new THREE.BoxGeometry(30, 0.01, 40);
-    const groundMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(0,130,0)' });
-    groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
-    groundMesh.position.y = 0.0; //this value must be slightly lower than the planeConstant (0.01) parameter above
-    scene.add(groundMesh);
-
-    // RED CUBE and CUBE's SHADOW
-    const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
-    const cubeMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(255,0,0)', emissive: 0x200000 });
-    cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
-    cube.position.z = -1;
-    scene.add(cube);
-
-    cubeShadow = new ShadowMesh(cube);
-    scene.add(cubeShadow);
-
-    // BLUE CYLINDER and CYLINDER's SHADOW
-    const cylinderGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2);
-    const cylinderMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(0,0,255)', emissive: 0x000020 });
-    cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
-    cylinder.position.z = -2.5;
-    scene.add(cylinder);
-
-    cylinderShadow = new ShadowMesh(cylinder);
-    scene.add(cylinderShadow);
-
-    // MAGENTA TORUS and TORUS' SHADOW
-    const torusGeometry = new THREE.TorusGeometry(1, 0.2, 10, 16, TWO_PI);
-    const torusMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(255,0,255)', emissive: 0x200020 });
-    torus = new THREE.Mesh(torusGeometry, torusMaterial);
-    torus.position.z = -6;
-    scene.add(torus);
-
-    torusShadow = new ShadowMesh(torus);
-    scene.add(torusShadow);
-
-    // WHITE SPHERE and SPHERE'S SHADOW
-    const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 10);
-    const sphereMaterial = new THREE.MeshPhongMaterial({ color: 'rgb(255,255,255)', emissive: 0x222222 });
-    sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
-    sphere.position.set(4, 0.5, 2);
-    scene.add(sphere);
-
-    sphereShadow = new ShadowMesh(sphere);
-    scene.add(sphereShadow);
-
-    // YELLOW PYRAMID and PYRAMID'S SHADOW
-    const pyramidGeometry = new THREE.CylinderGeometry(0, 0.5, 2, 4);
-    const pyramidMaterial = new THREE.MeshPhongMaterial({
-        color: 'rgb(255,255,0)',
-        emissive: 0x440000,
-        flatShading: true,
-        shininess: 0,
-    });
-    pyramid = new THREE.Mesh(pyramidGeometry, pyramidMaterial);
-    pyramid.position.set(-4, 1, 2);
-    scene.add(pyramid);
-
-    pyramidShadow = new ShadowMesh(pyramid);
-    scene.add(pyramidShadow);
-
-    document.getElementById('lightButton').addEventListener('click', lightButtonHandler);
-}
-
-function animate() {
-    frameTime = clock.getDelta();
-
-    cube.rotation.x += 1.0 * frameTime;
-    cube.rotation.y += 1.0 * frameTime;
-
-    cylinder.rotation.y += 1.0 * frameTime;
-    cylinder.rotation.z -= 1.0 * frameTime;
-
-    torus.rotation.x -= 1.0 * frameTime;
-    torus.rotation.y -= 1.0 * frameTime;
-
-    pyramid.rotation.y += 0.5 * frameTime;
-
-    horizontalAngle += 0.5 * frameTime;
-    if (horizontalAngle > TWO_PI) horizontalAngle -= TWO_PI;
-    cube.position.x = Math.sin(horizontalAngle) * 4;
-    cylinder.position.x = Math.sin(horizontalAngle) * -4;
-    torus.position.x = Math.cos(horizontalAngle) * 4;
-
-    verticalAngle += 1.5 * frameTime;
-    if (verticalAngle > TWO_PI) verticalAngle -= TWO_PI;
-    cube.position.y = Math.sin(verticalAngle) * 2 + 2.9;
-    cylinder.position.y = Math.sin(verticalAngle) * 2 + 3.1;
-    torus.position.y = Math.cos(verticalAngle) * 2 + 3.3;
-
-    // update the ShadowMeshes to follow their shadow-casting objects
-    cubeShadow.update(groundPlane, lightPosition4D);
-    cylinderShadow.update(groundPlane, lightPosition4D);
-    torusShadow.update(groundPlane, lightPosition4D);
-    sphereShadow.update(groundPlane, lightPosition4D);
-    pyramidShadow.update(groundPlane, lightPosition4D);
-
-    renderer.render(scene, camera);
-}
-
-function onWindowResize() {
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-
-    camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
-    camera.updateProjectionMatrix();
-}
-
-function lightButtonHandler() {
-    useDirectionalLight = !useDirectionalLight;
-
-    if (useDirectionalLight) {
-        scene.background.setHex(0x0096ff);
-
-        groundMesh.material.color.setHex(0x008200);
-        sunLight.position.set(5, 7, -1);
-        sunLight.lookAt(scene.position);
-
-        lightPosition4D.x = sunLight.position.x;
-        lightPosition4D.y = sunLight.position.y;
-        lightPosition4D.z = sunLight.position.z;
-        lightPosition4D.w = 0.001; // more of a directional Light value
-
-        arrowHelper1.visible = true;
-        arrowHelper2.visible = true;
-        arrowHelper3.visible = true;
-
-        lightSphere.visible = false;
-        lightHolder.visible = false;
-
-        document.getElementById('lightButton').value = 'Switch to PointLight';
-    } else {
-        scene.background.setHex(0x000000);
-
-        groundMesh.material.color.setHex(0x969696);
-
-        sunLight.position.set(0, 6, -2);
-        sunLight.lookAt(scene.position);
-        lightSphere.position.copy(sunLight.position);
-        lightHolder.position.copy(lightSphere.position);
-        lightHolder.position.y += 0.12;
-
-        lightPosition4D.x = sunLight.position.x;
-        lightPosition4D.y = sunLight.position.y;
-        lightPosition4D.z = sunLight.position.z;
-        lightPosition4D.w = 0.9; // more of a point Light value
-
-        arrowHelper1.visible = false;
-        arrowHelper2.visible = false;
-        arrowHelper3.visible = false;
-
-        lightSphere.visible = true;
-        lightHolder.visible = true;
-
-        document.getElementById('lightButton').value = 'Switch to THREE.DirectionalLight';
-    }
-}
diff --git a/examples-testing/examples/webgl_simple_gi.ts b/examples-testing/examples/webgl_simple_gi.ts
deleted file mode 100644
index 4ab6dc895..000000000
--- a/examples-testing/examples/webgl_simple_gi.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-class GIMesh extends THREE.Mesh {
-    copy(source) {
-        super.copy(source);
-
-        this.geometry = source.geometry.clone();
-
-        return this;
-    }
-}
-
-//
-
-const SimpleGI = function (renderer, scene) {
-    const SIZE = 32,
-        SIZE2 = SIZE * SIZE;
-
-    const camera = new THREE.PerspectiveCamera(90, 1, 0.01, 100);
-
-    scene.updateMatrixWorld(true);
-
-    let clone = scene.clone();
-    clone.matrixWorldAutoUpdate = false;
-
-    const rt = new THREE.WebGLRenderTarget(SIZE, SIZE);
-
-    const normalMatrix = new THREE.Matrix3();
-
-    const position = new THREE.Vector3();
-    const normal = new THREE.Vector3();
-
-    let bounces = 0;
-    let currentVertex = 0;
-
-    const color = new Float32Array(3);
-    const buffer = new Uint8Array(SIZE2 * 4);
-
-    function compute() {
-        if (bounces === 3) return;
-
-        const object = scene.children[0]; // torusKnot
-        const geometry = object.geometry;
-
-        const attributes = geometry.attributes;
-        const positions = attributes.position.array;
-        const normals = attributes.normal.array;
-
-        if (attributes.color === undefined) {
-            const colors = new Float32Array(positions.length);
-            geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage));
-        }
-
-        const colors = attributes.color.array;
-
-        const startVertex = currentVertex;
-        const totalVertex = positions.length / 3;
-
-        for (let i = 0; i < 32; i++) {
-            if (currentVertex >= totalVertex) break;
-
-            position.fromArray(positions, currentVertex * 3);
-            position.applyMatrix4(object.matrixWorld);
-
-            normal.fromArray(normals, currentVertex * 3);
-            normal.applyMatrix3(normalMatrix.getNormalMatrix(object.matrixWorld)).normalize();
-
-            camera.position.copy(position);
-            camera.lookAt(position.add(normal));
-
-            renderer.setRenderTarget(rt);
-            renderer.render(clone, camera);
-
-            renderer.readRenderTargetPixels(rt, 0, 0, SIZE, SIZE, buffer);
-
-            color[0] = 0;
-            color[1] = 0;
-            color[2] = 0;
-
-            for (let k = 0, kl = buffer.length; k < kl; k += 4) {
-                color[0] += buffer[k + 0];
-                color[1] += buffer[k + 1];
-                color[2] += buffer[k + 2];
-            }
-
-            colors[currentVertex * 3 + 0] = color[0] / (SIZE2 * 255);
-            colors[currentVertex * 3 + 1] = color[1] / (SIZE2 * 255);
-            colors[currentVertex * 3 + 2] = color[2] / (SIZE2 * 255);
-
-            currentVertex++;
-        }
-
-        attributes.color.addUpdateRange(startVertex * 3, (currentVertex - startVertex) * 3);
-        attributes.color.needsUpdate = true;
-
-        if (currentVertex >= totalVertex) {
-            clone = scene.clone();
-            clone.matrixWorldAutoUpdate = false;
-
-            bounces++;
-            currentVertex = 0;
-        }
-
-        requestAnimationFrame(compute);
-    }
-
-    requestAnimationFrame(compute);
-};
-
-//
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 4;
-
-    scene = new THREE.Scene();
-
-    // torus knot
-
-    const torusGeometry = new THREE.TorusKnotGeometry(0.75, 0.3, 128, 32, 1);
-    const material = new THREE.MeshBasicMaterial({ vertexColors: true });
-
-    const torusKnot = new GIMesh(torusGeometry, material);
-    scene.add(torusKnot);
-
-    // room
-
-    const materials = [];
-
-    for (let i = 0; i < 8; i++) {
-        materials.push(new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, side: THREE.BackSide }));
-    }
-
-    const boxGeometry = new THREE.BoxGeometry(3, 3, 3);
-
-    const box = new THREE.Mesh(boxGeometry, materials);
-    scene.add(box);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    new SimpleGI(renderer, scene);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.setRenderTarget(null);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_sprites.ts b/examples-testing/examples/webgl_sprites.ts
deleted file mode 100644
index 2e4189347..000000000
--- a/examples-testing/examples/webgl_sprites.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-let cameraOrtho, sceneOrtho;
-
-let spriteTL, spriteTR, spriteBL, spriteBR, spriteC;
-
-let mapC;
-
-let group;
-
-init();
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera = new THREE.PerspectiveCamera(60, width / height, 1, 2100);
-    camera.position.z = 1500;
-
-    cameraOrtho = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 1, 10);
-    cameraOrtho.position.z = 10;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1500, 2100);
-
-    sceneOrtho = new THREE.Scene();
-
-    // create sprites
-
-    const amount = 200;
-    const radius = 500;
-
-    const textureLoader = new THREE.TextureLoader();
-
-    textureLoader.load('textures/sprite0.png', createHUDSprites);
-    const mapB = textureLoader.load('textures/sprite1.png');
-    mapC = textureLoader.load('textures/sprite2.png');
-
-    mapB.colorSpace = THREE.SRGBColorSpace;
-    mapC.colorSpace = THREE.SRGBColorSpace;
-
-    group = new THREE.Group();
-
-    const materialC = new THREE.SpriteMaterial({ map: mapC, color: 0xffffff, fog: true });
-    const materialB = new THREE.SpriteMaterial({ map: mapB, color: 0xffffff, fog: true });
-
-    for (let a = 0; a < amount; a++) {
-        const x = Math.random() - 0.5;
-        const y = Math.random() - 0.5;
-        const z = Math.random() - 0.5;
-
-        let material;
-
-        if (z < 0) {
-            material = materialB.clone();
-        } else {
-            material = materialC.clone();
-            material.color.setHSL(0.5 * Math.random(), 0.75, 0.5);
-            material.map.offset.set(-0.5, -0.5);
-            material.map.repeat.set(2, 2);
-        }
-
-        const sprite = new THREE.Sprite(material);
-
-        sprite.position.set(x, y, z);
-        sprite.position.normalize();
-        sprite.position.multiplyScalar(radius);
-
-        group.add(sprite);
-    }
-
-    scene.add(group);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false; // To allow render overlay on top of sprited sphere
-
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createHUDSprites(texture) {
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const material = new THREE.SpriteMaterial({ map: texture });
-
-    const width = material.map.image.width;
-    const height = material.map.image.height;
-
-    spriteTL = new THREE.Sprite(material);
-    spriteTL.center.set(0.0, 1.0);
-    spriteTL.scale.set(width, height, 1);
-    sceneOrtho.add(spriteTL);
-
-    spriteTR = new THREE.Sprite(material);
-    spriteTR.center.set(1.0, 1.0);
-    spriteTR.scale.set(width, height, 1);
-    sceneOrtho.add(spriteTR);
-
-    spriteBL = new THREE.Sprite(material);
-    spriteBL.center.set(0.0, 0.0);
-    spriteBL.scale.set(width, height, 1);
-    sceneOrtho.add(spriteBL);
-
-    spriteBR = new THREE.Sprite(material);
-    spriteBR.center.set(1.0, 0.0);
-    spriteBR.scale.set(width, height, 1);
-    sceneOrtho.add(spriteBR);
-
-    spriteC = new THREE.Sprite(material);
-    spriteC.center.set(0.5, 0.5);
-    spriteC.scale.set(width, height, 1);
-    sceneOrtho.add(spriteC);
-
-    updateHUDSprites();
-}
-
-function updateHUDSprites() {
-    const width = window.innerWidth / 2;
-    const height = window.innerHeight / 2;
-
-    spriteTL.position.set(-width, height, 1); // top left
-    spriteTR.position.set(width, height, 1); // top right
-    spriteBL.position.set(-width, -height, 1); // bottom left
-    spriteBR.position.set(width, -height, 1); // bottom right
-    spriteC.position.set(0, 0, 1); // center
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    cameraOrtho.left = -width / 2;
-    cameraOrtho.right = width / 2;
-    cameraOrtho.top = height / 2;
-    cameraOrtho.bottom = -height / 2;
-    cameraOrtho.updateProjectionMatrix();
-
-    updateHUDSprites();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = Date.now() / 1000;
-
-    for (let i = 0, l = group.children.length; i < l; i++) {
-        const sprite = group.children[i];
-        const material = sprite.material;
-        const scale = Math.sin(time + sprite.position.x * 0.01) * 0.3 + 1.0;
-
-        let imageWidth = 1;
-        let imageHeight = 1;
-
-        if (material.map && material.map.image && material.map.image.width) {
-            imageWidth = material.map.image.width;
-            imageHeight = material.map.image.height;
-        }
-
-        sprite.material.rotation += 0.1 * (i / l);
-        sprite.scale.set(scale * imageWidth, scale * imageHeight, 1.0);
-
-        if (material.map !== mapC) {
-            material.opacity = Math.sin(time + sprite.position.x * 0.01) * 0.4 + 0.6;
-        }
-    }
-
-    group.rotation.x = time * 0.5;
-    group.rotation.y = time * 0.75;
-    group.rotation.z = time * 1.0;
-
-    renderer.clear();
-    renderer.render(scene, camera);
-    renderer.clearDepth();
-    renderer.render(sceneOrtho, cameraOrtho);
-}
diff --git a/examples-testing/examples/webgl_test_memory.ts b/examples-testing/examples/webgl_test_memory.ts
deleted file mode 100644
index f5d0e112d..000000000
--- a/examples-testing/examples/webgl_test_memory.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 200;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-}
-
-function createImage() {
-    const canvas = document.createElement('canvas');
-    canvas.width = 256;
-    canvas.height = 256;
-
-    const context = canvas.getContext('2d');
-    context.fillStyle =
-        'rgb(' +
-        Math.floor(Math.random() * 256) +
-        ',' +
-        Math.floor(Math.random() * 256) +
-        ',' +
-        Math.floor(Math.random() * 256) +
-        ')';
-    context.fillRect(0, 0, 256, 256);
-
-    return canvas;
-}
-
-//
-
-function animate() {
-    const geometry = new THREE.SphereGeometry(50, Math.random() * 64, Math.random() * 32);
-
-    const texture = new THREE.CanvasTexture(createImage());
-
-    const material = new THREE.MeshBasicMaterial({ map: texture, wireframe: true });
-
-    const mesh = new THREE.Mesh(geometry, material);
-
-    scene.add(mesh);
-
-    renderer.render(scene, camera);
-
-    scene.remove(mesh);
-
-    // clean up
-
-    geometry.dispose();
-    material.dispose();
-    texture.dispose();
-}
diff --git a/examples-testing/examples/webgl_test_memory2.ts b/examples-testing/examples/webgl_test_memory2.ts
deleted file mode 100644
index 366a27914..000000000
--- a/examples-testing/examples/webgl_test_memory2.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as THREE from 'three';
-
-const N = 100;
-
-let container;
-
-let camera, scene, renderer;
-
-let geometry;
-
-const meshes = [];
-
-let fragmentShader, vertexShader;
-
-init();
-setInterval(render, 1000 / 60);
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    vertexShader = document.getElementById('vertexShader').textContent;
-    fragmentShader = document.getElementById('fragmentShader').textContent;
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 2000;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    geometry = new THREE.SphereGeometry(15, 64, 32);
-
-    for (let i = 0; i < N; i++) {
-        const material = new THREE.ShaderMaterial({
-            vertexShader: vertexShader,
-            fragmentShader: generateFragmentShader(),
-        });
-
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = (0.5 - Math.random()) * 1000;
-        mesh.position.y = (0.5 - Math.random()) * 1000;
-        mesh.position.z = (0.5 - Math.random()) * 1000;
-
-        scene.add(mesh);
-
-        meshes.push(mesh);
-    }
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    container.appendChild(renderer.domElement);
-}
-
-//
-
-function generateFragmentShader() {
-    return fragmentShader.replace('XXX', Math.random() + ',' + Math.random() + ',' + Math.random());
-}
-
-function render() {
-    for (let i = 0; i < N; i++) {
-        const mesh = meshes[i];
-        mesh.material = new THREE.ShaderMaterial({
-            vertexShader: vertexShader,
-            fragmentShader: generateFragmentShader(),
-        });
-    }
-
-    renderer.render(scene, camera);
-
-    console.log('before', renderer.info.programs.length);
-
-    for (let i = 0; i < N; i++) {
-        const mesh = meshes[i];
-        mesh.material.dispose();
-    }
-
-    console.log('after', renderer.info.programs.length);
-}
diff --git a/examples-testing/examples/webgl_test_wide_gamut.ts b/examples-testing/examples/webgl_test_wide_gamut.ts
deleted file mode 100644
index 693dd4593..000000000
--- a/examples-testing/examples/webgl_test_wide_gamut.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as THREE from 'three';
-
-import WebGL from 'three/addons/capabilities/WebGL.js';
-
-let container, camera, renderer, loader;
-let sceneL, sceneR, textureL, textureR;
-
-let sliderPos = window.innerWidth / 2;
-
-const slider = document.querySelector('.slider');
-
-const isP3Context = WebGL.isColorSpaceAvailable(THREE.DisplayP3ColorSpace);
-
-if (isP3Context) {
-    THREE.ColorManagement.workingColorSpace = THREE.LinearDisplayP3ColorSpace;
-}
-
-init();
-
-function init() {
-    container = document.querySelector('.container');
-
-    sceneL = new THREE.Scene();
-    sceneR = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 6;
-
-    loader = new THREE.TextureLoader();
-
-    initTextures();
-    initSlider();
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.setScissorTest(true);
-    container.appendChild(renderer.domElement);
-
-    if (isP3Context && window.matchMedia('( color-gamut: p3 )').matches) {
-        renderer.outputColorSpace = THREE.DisplayP3ColorSpace;
-    }
-
-    window.addEventListener('resize', onWindowResize);
-    window.matchMedia('( color-gamut: p3 )').addEventListener('change', onGamutChange);
-}
-
-async function initTextures() {
-    const path = 'textures/wide_gamut/logo_{colorSpace}.png';
-
-    textureL = await loader.loadAsync(path.replace('{colorSpace}', 'srgb'));
-    textureR = await loader.loadAsync(path.replace('{colorSpace}', 'p3'));
-
-    textureL.colorSpace = THREE.SRGBColorSpace;
-    textureR.colorSpace = THREE.DisplayP3ColorSpace;
-
-    sceneL.background = THREE.TextureUtils.contain(textureL, window.innerWidth / window.innerHeight);
-    sceneR.background = THREE.TextureUtils.contain(textureR, window.innerWidth / window.innerHeight);
-}
-
-function initSlider() {
-    function onPointerDown() {
-        if (event.isPrimary === false) return;
-
-        window.addEventListener('pointermove', onPointerMove);
-        window.addEventListener('pointerup', onPointerUp);
-    }
-
-    function onPointerUp() {
-        window.removeEventListener('pointermove', onPointerMove);
-        window.removeEventListener('pointerup', onPointerUp);
-    }
-
-    function onPointerMove(e) {
-        if (event.isPrimary === false) return;
-
-        updateSlider(e.pageX);
-    }
-
-    updateSlider(sliderPos);
-
-    slider.style.touchAction = 'none'; // disable touch scroll
-    slider.addEventListener('pointerdown', onPointerDown);
-}
-
-function updateSlider(offset) {
-    sliderPos = Math.max(10, Math.min(window.innerWidth - 10, offset));
-
-    slider.style.left = sliderPos - slider.offsetWidth / 2 + 'px';
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    THREE.TextureUtils.contain(sceneL.background, window.innerWidth / window.innerHeight);
-    THREE.TextureUtils.contain(sceneR.background, window.innerWidth / window.innerHeight);
-
-    updateSlider(sliderPos);
-}
-
-function onGamutChange({ matches }) {
-    renderer.outputColorSpace = isP3Context && matches ? THREE.DisplayP3ColorSpace : THREE.SRGBColorSpace;
-
-    textureL.needsUpdate = true;
-    textureR.needsUpdate = true;
-}
-
-function animate() {
-    renderer.setScissor(0, 0, sliderPos, window.innerHeight);
-    renderer.render(sceneL, camera);
-
-    renderer.setScissor(sliderPos, 0, window.innerWidth, window.innerHeight);
-    renderer.render(sceneR, camera);
-}
diff --git a/examples-testing/examples/webgl_texture2darray_compressed.ts b/examples-testing/examples/webgl_texture2darray_compressed.ts
deleted file mode 100644
index f263be706..000000000
--- a/examples-testing/examples/webgl_texture2darray_compressed.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-
-let camera, scene, mesh, renderer, stats, clock;
-
-const planeWidth = 50;
-const planeHeight = 25;
-
-let depthStep = 1;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
-    camera.position.z = 70;
-
-    scene = new THREE.Scene();
-
-    //
-    clock = new THREE.Clock();
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const ktx2Loader = new KTX2Loader();
-    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
-    ktx2Loader.detectSupport(renderer);
-
-    ktx2Loader.load('textures/spiritedaway.ktx2', function (texturearray) {
-        const material = new THREE.ShaderMaterial({
-            uniforms: {
-                diffuse: { value: texturearray },
-                depth: { value: 55 },
-                size: { value: new THREE.Vector2(planeWidth, planeHeight) },
-            },
-            vertexShader: document.getElementById('vs').textContent.trim(),
-            fragmentShader: document.getElementById('fs').textContent.trim(),
-            glslVersion: THREE.GLSL3,
-        });
-
-        const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight);
-
-        mesh = new THREE.Mesh(geometry, material);
-
-        scene.add(mesh);
-    });
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    if (mesh) {
-        const delta = clock.getDelta() * 10;
-
-        depthStep += delta;
-
-        const value = depthStep % 5;
-
-        mesh.material.uniforms['depth'].value = value;
-    }
-
-    render();
-    stats.update();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_texture2darray_layerupdate.ts b/examples-testing/examples/webgl_texture2darray_layerupdate.ts
deleted file mode 100644
index 0cc136cb7..000000000
--- a/examples-testing/examples/webgl_texture2darray_layerupdate.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-
-let camera, scene, mesh, renderer;
-
-const planeWidth = 20;
-const planeHeight = 10;
-
-init();
-
-async function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
-    camera.position.z = 70;
-
-    scene = new THREE.Scene();
-
-    // Configure the renderer.
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    container.appendChild(renderer.domElement);
-
-    // Configure the KTX2 loader.
-
-    const ktx2Loader = new KTX2Loader();
-    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
-    ktx2Loader.detectSupport(renderer);
-
-    // Load several KTX2 textures which will later be used to modify
-    // specific texture array layers.
-
-    const spiritedaway = await ktx2Loader.loadAsync('textures/spiritedaway.ktx2');
-
-    // Create a texture array for rendering.
-
-    const layerByteLength = THREE.TextureUtils.getByteLength(
-        spiritedaway.image.width,
-        spiritedaway.image.height,
-        spiritedaway.format,
-        spiritedaway.type,
-    );
-
-    const textureArray = new THREE.CompressedArrayTexture(
-        [
-            {
-                data: new Uint8Array(layerByteLength * 3),
-                width: spiritedaway.image.width,
-                height: spiritedaway.image.height,
-            },
-        ],
-        spiritedaway.image.width,
-        spiritedaway.image.height,
-        3,
-        spiritedaway.format,
-        spiritedaway.type,
-    );
-
-    // Setup the GUI
-
-    const formData = {
-        srcLayer: 0,
-        destLayer: 0,
-        transfer() {
-            const layerElementLength = layerByteLength / spiritedaway.mipmaps[0].data.BYTES_PER_ELEMENT;
-            textureArray.mipmaps[0].data.set(
-                spiritedaway.mipmaps[0].data.subarray(
-                    layerElementLength * (formData.srcLayer % spiritedaway.image.depth),
-                    layerElementLength * ((formData.srcLayer % spiritedaway.image.depth) + 1),
-                ),
-                layerByteLength * formData.destLayer,
-            );
-            textureArray.addLayerUpdate(formData.destLayer);
-            textureArray.needsUpdate = true;
-
-            renderer.render(scene, camera);
-        },
-    };
-
-    const gui = new GUI();
-    gui.add(formData, 'srcLayer', 0, spiritedaway.image.depth - 1, 1);
-    gui.add(formData, 'destLayer', 0, textureArray.image.depth - 1, 1);
-    gui.add(formData, 'transfer');
-
-    /// Setup the scene.
-
-    const material = new THREE.ShaderMaterial({
-        uniforms: {
-            diffuse: { value: textureArray },
-            size: { value: new THREE.Vector2(planeWidth, planeHeight) },
-        },
-        vertexShader: document.getElementById('vs').textContent.trim(),
-        fragmentShader: document.getElementById('fs').textContent.trim(),
-        glslVersion: THREE.GLSL3,
-    });
-
-    const geometry = new THREE.InstancedBufferGeometry();
-    geometry.copy(new THREE.PlaneGeometry(planeWidth, planeHeight));
-    geometry.instanceCount = 3;
-
-    const instancedIndexAttribute = new THREE.InstancedBufferAttribute(new Uint16Array([0, 1, 2]), 1, false, 1);
-    instancedIndexAttribute.gpuType = THREE.IntType;
-    geometry.setAttribute('instancedIndex', instancedIndexAttribute);
-
-    mesh = new THREE.InstancedMesh(geometry, material, 3);
-
-    scene.add(mesh);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // Initialize the texture array by first rendering the spirited away
-    // frames in order.
-
-    textureArray.mipmaps[0].data.set(spiritedaway.mipmaps[0].data.subarray(0, textureArray.mipmaps[0].data.length));
-    textureArray.needsUpdate = true;
-    renderer.render(scene, camera);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_texture3d.ts b/examples-testing/examples/webgl_texture3d.ts
deleted file mode 100644
index 977dbadb7..000000000
--- a/examples-testing/examples/webgl_texture3d.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { NRRDLoader } from 'three/addons/loaders/NRRDLoader.js';
-import { VolumeRenderShader1 } from 'three/addons/shaders/VolumeShader.js';
-
-let renderer, scene, camera, controls, material, volconfig, cmtextures;
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    // Create renderer
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    // Create camera (The volume renderer does not work very well with perspective yet)
-    const h = 512; // frustum height
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera((-h * aspect) / 2, (h * aspect) / 2, h / 2, -h / 2, 1, 1000);
-    camera.position.set(-64, -64, 128);
-    camera.up.set(0, 0, 1); // In our data, z is up
-
-    // Create controls
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render);
-    controls.target.set(64, 64, 128);
-    controls.minZoom = 0.5;
-    controls.maxZoom = 4;
-    controls.enablePan = false;
-    controls.update();
-
-    // scene.add( new AxesHelper( 128 ) );
-
-    // Lighting is baked into the shader a.t.m.
-    // let dirLight = new DirectionalLight( 0xffffff );
-
-    // The gui for interaction
-    volconfig = { clim1: 0, clim2: 1, renderstyle: 'iso', isothreshold: 0.15, colormap: 'viridis' };
-    const gui = new GUI();
-    gui.add(volconfig, 'clim1', 0, 1, 0.01).onChange(updateUniforms);
-    gui.add(volconfig, 'clim2', 0, 1, 0.01).onChange(updateUniforms);
-    gui.add(volconfig, 'colormap', { gray: 'gray', viridis: 'viridis' }).onChange(updateUniforms);
-    gui.add(volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' }).onChange(updateUniforms);
-    gui.add(volconfig, 'isothreshold', 0, 1, 0.01).onChange(updateUniforms);
-
-    // Load the data ...
-    new NRRDLoader().load('models/nrrd/stent.nrrd', function (volume) {
-        // Texture to hold the volume. We have scalars, so we put our data in the red channel.
-        // THREEJS will select R32F (33326) based on the THREE.RedFormat and THREE.FloatType.
-        // Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
-        // TODO: look the dtype up in the volume metadata
-        const texture = new THREE.Data3DTexture(volume.data, volume.xLength, volume.yLength, volume.zLength);
-        texture.format = THREE.RedFormat;
-        texture.type = THREE.FloatType;
-        texture.minFilter = texture.magFilter = THREE.LinearFilter;
-        texture.unpackAlignment = 1;
-        texture.needsUpdate = true;
-
-        // Colormap textures
-        cmtextures = {
-            viridis: new THREE.TextureLoader().load('textures/cm_viridis.png', render),
-            gray: new THREE.TextureLoader().load('textures/cm_gray.png', render),
-        };
-
-        // Material
-        const shader = VolumeRenderShader1;
-
-        const uniforms = THREE.UniformsUtils.clone(shader.uniforms);
-
-        uniforms['u_data'].value = texture;
-        uniforms['u_size'].value.set(volume.xLength, volume.yLength, volume.zLength);
-        uniforms['u_clim'].value.set(volconfig.clim1, volconfig.clim2);
-        uniforms['u_renderstyle'].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
-        uniforms['u_renderthreshold'].value = volconfig.isothreshold; // For ISO renderstyle
-        uniforms['u_cmdata'].value = cmtextures[volconfig.colormap];
-
-        material = new THREE.ShaderMaterial({
-            uniforms: uniforms,
-            vertexShader: shader.vertexShader,
-            fragmentShader: shader.fragmentShader,
-            side: THREE.BackSide, // The volume shader uses the backface as its "reference point"
-        });
-
-        // THREE.Mesh
-        const geometry = new THREE.BoxGeometry(volume.xLength, volume.yLength, volume.zLength);
-        geometry.translate(volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5);
-
-        const mesh = new THREE.Mesh(geometry, material);
-        scene.add(mesh);
-
-        render();
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function updateUniforms() {
-    material.uniforms['u_clim'].value.set(volconfig.clim1, volconfig.clim2);
-    material.uniforms['u_renderstyle'].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
-    material.uniforms['u_renderthreshold'].value = volconfig.isothreshold; // For ISO renderstyle
-    material.uniforms['u_cmdata'].value = cmtextures[volconfig.colormap];
-
-    render();
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    const aspect = window.innerWidth / window.innerHeight;
-
-    const frustumHeight = camera.top - camera.bottom;
-
-    camera.left = (-frustumHeight * aspect) / 2;
-    camera.right = (frustumHeight * aspect) / 2;
-
-    camera.updateProjectionMatrix();
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_texture3d_partialupdate.ts b/examples-testing/examples/webgl_texture3d_partialupdate.ts
deleted file mode 100644
index 1ad6d2646..000000000
--- a/examples-testing/examples/webgl_texture3d_partialupdate.ts
+++ /dev/null
@@ -1,326 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const INITIAL_CLOUD_SIZE = 128;
-
-let renderer, scene, camera;
-let mesh;
-let prevTime = performance.now();
-let cloudTexture = null;
-
-init();
-
-function generateCloudTexture(size, scaleFactor = 1.0) {
-    const data = new Uint8Array(size * size * size);
-    const scale = (scaleFactor * 10.0) / size;
-
-    let i = 0;
-    const perlin = new ImprovedNoise();
-    const vector = new THREE.Vector3();
-
-    for (let z = 0; z < size; z++) {
-        for (let y = 0; y < size; y++) {
-            for (let x = 0; x < size; x++) {
-                const dist = vector
-                    .set(x, y, z)
-                    .subScalar(size / 2)
-                    .divideScalar(size)
-                    .length();
-                const fadingFactor = (1.0 - dist) * (1.0 - dist);
-                data[i] = (128 + 128 * perlin.noise((x * scale) / 1.5, y * scale, (z * scale) / 1.5)) * fadingFactor;
-
-                i++;
-            }
-        }
-    }
-
-    return new THREE.Data3DTexture(data, size, size, size);
-}
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 1.5);
-
-    new OrbitControls(camera, renderer.domElement);
-
-    // Sky
-
-    const canvas = document.createElement('canvas');
-    canvas.width = 1;
-    canvas.height = 32;
-
-    const context = canvas.getContext('2d');
-    const gradient = context.createLinearGradient(0, 0, 0, 32);
-    gradient.addColorStop(0.0, '#014a84');
-    gradient.addColorStop(0.5, '#0561a0');
-    gradient.addColorStop(1.0, '#437ab6');
-    context.fillStyle = gradient;
-    context.fillRect(0, 0, 1, 32);
-
-    const skyMap = new THREE.CanvasTexture(canvas);
-    skyMap.colorSpace = THREE.SRGBColorSpace;
-
-    const sky = new THREE.Mesh(
-        new THREE.SphereGeometry(10),
-        new THREE.MeshBasicMaterial({ map: skyMap, side: THREE.BackSide }),
-    );
-    scene.add(sky);
-
-    // Texture
-
-    const texture = new THREE.Data3DTexture(
-        new Uint8Array(INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE * INITIAL_CLOUD_SIZE).fill(0),
-        INITIAL_CLOUD_SIZE,
-        INITIAL_CLOUD_SIZE,
-        INITIAL_CLOUD_SIZE,
-    );
-    texture.format = THREE.RedFormat;
-    texture.minFilter = THREE.LinearFilter;
-    texture.magFilter = THREE.LinearFilter;
-    texture.unpackAlignment = 1;
-    texture.needsUpdate = true;
-
-    cloudTexture = texture;
-
-    // Material
-
-    const vertexShader = /* glsl */ `
-					in vec3 position;
-
-					uniform mat4 modelMatrix;
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-					uniform vec3 cameraPos;
-
-					out vec3 vOrigin;
-					out vec3 vDirection;
-
-					void main() {
-						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
-
-						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
-						vDirection = position - vOrigin;
-
-						gl_Position = projectionMatrix * mvPosition;
-					}
-				`;
-
-    const fragmentShader = /* glsl */ `
-					precision highp float;
-					precision highp sampler3D;
-
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-
-					in vec3 vOrigin;
-					in vec3 vDirection;
-
-					out vec4 color;
-
-					uniform vec3 base;
-					uniform sampler3D map;
-
-					uniform float threshold;
-					uniform float range;
-					uniform float opacity;
-					uniform float steps;
-					uniform float frame;
-
-					uint wang_hash(uint seed)
-					{
-							seed = (seed ^ 61u) ^ (seed >> 16u);
-							seed *= 9u;
-							seed = seed ^ (seed >> 4u);
-							seed *= 0x27d4eb2du;
-							seed = seed ^ (seed >> 15u);
-							return seed;
-					}
-
-					float randomFloat(inout uint seed)
-					{
-							return float(wang_hash(seed)) / 4294967296.;
-					}
-
-					vec2 hitBox( vec3 orig, vec3 dir ) {
-						const vec3 box_min = vec3( - 0.5 );
-						const vec3 box_max = vec3( 0.5 );
-						vec3 inv_dir = 1.0 / dir;
-						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
-						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
-						vec3 tmin = min( tmin_tmp, tmax_tmp );
-						vec3 tmax = max( tmin_tmp, tmax_tmp );
-						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
-						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
-						return vec2( t0, t1 );
-					}
-
-					float sample1( vec3 p ) {
-						return texture( map, p ).r;
-					}
-
-					float shading( vec3 coord ) {
-						float step = 0.01;
-						return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
-					}
-
-					vec4 linearToSRGB( in vec4 value ) {
-						return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
-					}
-
-					void main(){
-						vec3 rayDir = normalize( vDirection );
-						vec2 bounds = hitBox( vOrigin, rayDir );
-
-						if ( bounds.x > bounds.y ) discard;
-
-						bounds.x = max( bounds.x, 0.0 );
-
-						vec3 p = vOrigin + bounds.x * rayDir;
-						vec3 inc = 1.0 / abs( rayDir );
-						float delta = min( inc.x, min( inc.y, inc.z ) );
-						delta /= steps;
-
-						// Jitter
-
-						// Nice little seed from
-						// https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
-						uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
-						vec3 size = vec3( textureSize( map, 0 ) );
-						float randNum = randomFloat( seed ) * 2.0 - 1.0;
-						p += rayDir * randNum * ( 1.0 / size );
-
-						//
-
-						vec4 ac = vec4( base, 0.0 );
-
-						for ( float t = bounds.x; t < bounds.y; t += delta ) {
-
-							float d = sample1( p + 0.5 );
-
-							d = smoothstep( threshold - range, threshold + range, d ) * opacity;
-
-							float col = shading( p + 0.5 ) * 3.0 + ( ( p.x + p.y ) * 0.25 ) + 0.2;
-
-							ac.rgb += ( 1.0 - ac.a ) * d * col;
-
-							ac.a += ( 1.0 - ac.a ) * d;
-
-							if ( ac.a >= 0.95 ) break;
-
-							p += rayDir * delta;
-
-						}
-
-						color = linearToSRGB( ac );
-
-						if ( color.a == 0.0 ) discard;
-
-					}
-				`;
-
-    const geometry = new THREE.BoxGeometry(1, 1, 1);
-    const material = new THREE.RawShaderMaterial({
-        glslVersion: THREE.GLSL3,
-        uniforms: {
-            base: { value: new THREE.Color(0x798aa0) },
-            map: { value: texture },
-            cameraPos: { value: new THREE.Vector3() },
-            threshold: { value: 0.25 },
-            opacity: { value: 0.25 },
-            range: { value: 0.1 },
-            steps: { value: 100 },
-            frame: { value: 0 },
-        },
-        vertexShader,
-        fragmentShader,
-        side: THREE.BackSide,
-        transparent: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const parameters = {
-        threshold: 0.25,
-        opacity: 0.25,
-        range: 0.1,
-        steps: 100,
-    };
-
-    function update() {
-        material.uniforms.threshold.value = parameters.threshold;
-        material.uniforms.opacity.value = parameters.opacity;
-        material.uniforms.range.value = parameters.range;
-        material.uniforms.steps.value = parameters.steps;
-    }
-
-    const gui = new GUI();
-    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'opacity', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'range', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'steps', 0, 200, 1).onChange(update);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-let curr = 0;
-const countPerRow = 4;
-const countPerSlice = countPerRow * countPerRow;
-const sliceCount = 4;
-const totalCount = sliceCount * countPerSlice;
-const margins = 8;
-
-const perElementPaddedSize = (INITIAL_CLOUD_SIZE - margins) / countPerRow;
-const perElementSize = Math.floor((INITIAL_CLOUD_SIZE - 1) / countPerRow);
-
-function animate() {
-    const time = performance.now();
-    if (time - prevTime > 1500.0 && curr < totalCount) {
-        const position = new THREE.Vector3(
-            Math.floor(curr % countPerRow) * perElementSize + margins * 0.5,
-            Math.floor((curr % countPerSlice) / countPerRow) * perElementSize + margins * 0.5,
-            Math.floor(curr / countPerSlice) * perElementSize + margins * 0.5,
-        ).floor();
-
-        const maxDimension = perElementPaddedSize - 1;
-        const box = new THREE.Box3(
-            new THREE.Vector3(0, 0, 0),
-            new THREE.Vector3(maxDimension, maxDimension, maxDimension),
-        );
-        const scaleFactor = (Math.random() + 0.5) * 0.5;
-        const source = generateCloudTexture(perElementPaddedSize, scaleFactor);
-
-        renderer.copyTextureToTexture3D(source, cloudTexture, box, position);
-
-        prevTime = time;
-
-        curr++;
-    }
-
-    mesh.material.uniforms.cameraPos.value.copy(camera.position);
-    // mesh.rotation.y = - performance.now() / 7500;
-
-    mesh.material.uniforms.frame.value++;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_tonemapping.ts b/examples-testing/examples/webgl_tonemapping.ts
deleted file mode 100644
index 08115cf3e..000000000
--- a/examples-testing/examples/webgl_tonemapping.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let mesh, renderer, scene, camera, controls;
-let gui,
-    guiExposure = null;
-
-const params = {
-    exposure: 1.0,
-    toneMapping: 'AgX',
-    blurriness: 0.3,
-    intensity: 1.0,
-};
-
-const toneMappingOptions = {
-    None: THREE.NoToneMapping,
-    Linear: THREE.LinearToneMapping,
-    Reinhard: THREE.ReinhardToneMapping,
-    Cineon: THREE.CineonToneMapping,
-    ACESFilmic: THREE.ACESFilmicToneMapping,
-    AgX: THREE.AgXToneMapping,
-    Neutral: THREE.NeutralToneMapping,
-    Custom: THREE.CustomToneMapping,
-};
-
-init().catch(function (err) {
-    console.error(err);
-});
-
-async function init() {
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = toneMappingOptions[params.toneMapping];
-    renderer.toneMappingExposure = params.exposure;
-
-    // Set CustomToneMapping to Uncharted2
-    // source: http://filmicworlds.com/blog/filmic-tonemapping-operators/
-
-    THREE.ShaderChunk.tonemapping_pars_fragment = THREE.ShaderChunk.tonemapping_pars_fragment.replace(
-        'vec3 CustomToneMapping( vec3 color ) { return color; }',
-
-        `#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
-
-					float toneMappingWhitePoint = 1.0;
-
-					vec3 CustomToneMapping( vec3 color ) {
-						color *= toneMappingExposure;
-						return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );
-
-					}`,
-    );
-
-    scene = new THREE.Scene();
-    scene.backgroundBlurriness = params.blurriness;
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.enableZoom = false;
-    controls.enablePan = false;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-
-    const [texture, gltf] = await Promise.all([
-        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
-        gltfLoader.loadAsync('DamagedHelmet.gltf'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.environment = texture;
-
-    // model
-
-    mesh = gltf.scene.getObjectByName('node_damagedHelmet_-6514');
-    scene.add(mesh);
-
-    render();
-
-    window.addEventListener('resize', onWindowResize);
-
-    gui = new GUI();
-    const toneMappingFolder = gui.addFolder('tone mapping');
-
-    toneMappingFolder
-        .add(params, 'toneMapping', Object.keys(toneMappingOptions))
-
-        .onChange(function () {
-            updateGUI(toneMappingFolder);
-
-            renderer.toneMapping = toneMappingOptions[params.toneMapping];
-            render();
-        });
-
-    const backgroundFolder = gui.addFolder('background');
-
-    backgroundFolder
-        .add(params, 'blurriness', 0, 1)
-
-        .onChange(function (value) {
-            scene.backgroundBlurriness = value;
-            render();
-        });
-
-    backgroundFolder
-        .add(params, 'intensity', 0, 1)
-
-        .onChange(function (value) {
-            scene.backgroundIntensity = value;
-            render();
-        });
-
-    updateGUI(toneMappingFolder);
-
-    gui.open();
-}
-
-function updateGUI(folder) {
-    if (guiExposure !== null) {
-        guiExposure.destroy();
-        guiExposure = null;
-    }
-
-    if (params.toneMapping !== 'None') {
-        guiExposure = folder
-            .add(params, 'exposure', 0, 2)
-
-            .onChange(function () {
-                renderer.toneMappingExposure = params.exposure;
-                render();
-            });
-    }
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_ubo.ts b/examples-testing/examples/webgl_ubo.ts
deleted file mode 100644
index 01064f115..000000000
--- a/examples-testing/examples/webgl_ubo.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, clock;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 25);
-
-    scene = new THREE.Scene();
-    camera.lookAt(scene.position);
-
-    clock = new THREE.Clock();
-
-    // geometry
-
-    const geometry1 = new THREE.TetrahedronGeometry();
-    const geometry2 = new THREE.BoxGeometry();
-
-    // texture
-
-    const texture = new THREE.TextureLoader().load('textures/crate.gif');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    // uniforms groups
-
-    // Camera and lighting related data are perfect examples of using UBOs since you have to store these
-    // data just once. They can be shared across all shader programs.
-
-    const cameraUniformsGroup = new THREE.UniformsGroup();
-    cameraUniformsGroup.setName('ViewData');
-    cameraUniformsGroup.add(new THREE.Uniform(camera.projectionMatrix)); // projection matrix
-    cameraUniformsGroup.add(new THREE.Uniform(camera.matrixWorldInverse)); // view matrix
-
-    const lightingUniformsGroup = new THREE.UniformsGroup();
-    lightingUniformsGroup.setName('LightingData');
-    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Vector3(0, 0, 10))); // light position
-    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0x7c7c7c))); // ambient color
-    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0xd5d5d5))); // diffuse color
-    lightingUniformsGroup.add(new THREE.Uniform(new THREE.Color(0xe7e7e7))); // specular color
-    lightingUniformsGroup.add(new THREE.Uniform(64)); // shininess
-
-    // materials
-
-    const material1 = new THREE.RawShaderMaterial({
-        uniforms: {
-            modelMatrix: { value: null },
-            normalMatrix: { value: null },
-            color: { value: null },
-        },
-        vertexShader: document.getElementById('vertexShader1').textContent,
-        fragmentShader: document.getElementById('fragmentShader1').textContent,
-        glslVersion: THREE.GLSL3,
-    });
-
-    const material2 = new THREE.RawShaderMaterial({
-        uniforms: {
-            modelMatrix: { value: null },
-            diffuseMap: { value: null },
-        },
-        vertexShader: document.getElementById('vertexShader2').textContent,
-        fragmentShader: document.getElementById('fragmentShader2').textContent,
-        glslVersion: THREE.GLSL3,
-    });
-
-    // meshes
-
-    for (let i = 0; i < 200; i++) {
-        let mesh;
-
-        if (i % 2 === 0) {
-            mesh = new THREE.Mesh(geometry1, material1.clone());
-
-            mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
-            mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
-            mesh.material.uniforms.normalMatrix.value = mesh.normalMatrix;
-            mesh.material.uniforms.color.value = new THREE.Color(0xffffff * Math.random());
-        } else {
-            mesh = new THREE.Mesh(geometry2, material2.clone());
-
-            mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
-            mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
-            mesh.material.uniforms.diffuseMap.value = texture;
-        }
-
-        scene.add(mesh);
-
-        const s = 1 + Math.random() * 0.5;
-
-        mesh.scale.x = s;
-        mesh.scale.y = s;
-        mesh.scale.z = s;
-
-        mesh.rotation.x = Math.random() * Math.PI;
-        mesh.rotation.y = Math.random() * Math.PI;
-        mesh.rotation.z = Math.random() * Math.PI;
-
-        mesh.position.x = Math.random() * 40 - 20;
-        mesh.position.y = Math.random() * 40 - 20;
-        mesh.position.z = Math.random() * 20 - 10;
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize, false);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const delta = clock.getDelta();
-
-    scene.traverse(function (child) {
-        if (child.isMesh) {
-            child.rotation.x += delta * 0.5;
-            child.rotation.y += delta * 0.3;
-        }
-    });
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_ubo_arrays.ts b/examples-testing/examples/webgl_ubo_arrays.ts
deleted file mode 100644
index d846e1443..000000000
--- a/examples-testing/examples/webgl_ubo_arrays.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, clock, stats;
-
-let lightingUniformsGroup, lightCenters;
-
-const container = document.getElementById('container');
-
-const pointLightsMax = 300;
-
-const api = {
-    count: 200,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 50, 50);
-
-    scene = new THREE.Scene();
-    camera.lookAt(scene.position);
-
-    clock = new THREE.Clock();
-
-    // geometry
-
-    const geometry = new THREE.SphereGeometry();
-
-    // uniforms groups
-
-    lightingUniformsGroup = new THREE.UniformsGroup();
-    lightingUniformsGroup.setName('LightingData');
-
-    const data = [];
-    const dataColors = [];
-    lightCenters = [];
-
-    for (let i = 0; i < pointLightsMax; i++) {
-        const col = new THREE.Color(0xffffff * Math.random()).toArray();
-        const x = Math.random() * 50 - 25;
-        const z = Math.random() * 50 - 25;
-
-        data.push(new THREE.Uniform(new THREE.Vector4(x, 1, z, 0))); // light position
-        dataColors.push(new THREE.Uniform(new THREE.Vector4(col[0], col[1], col[2], 0))); // light color
-
-        // Store the center positions
-        lightCenters.push({ x, z });
-    }
-
-    lightingUniformsGroup.add(data); // light position
-    lightingUniformsGroup.add(dataColors); // light position
-    lightingUniformsGroup.add(new THREE.Uniform(pointLightsMax)); // light position
-
-    const cameraUniformsGroup = new THREE.UniformsGroup();
-    cameraUniformsGroup.setName('ViewData');
-    cameraUniformsGroup.add(new THREE.Uniform(camera.projectionMatrix)); // projection matrix
-    cameraUniformsGroup.add(new THREE.Uniform(camera.matrixWorldInverse)); // view matrix
-
-    const material = new THREE.RawShaderMaterial({
-        uniforms: {
-            modelMatrix: { value: null },
-            normalMatrix: { value: null },
-        },
-        // uniformsGroups: [ cameraUniformsGroup, lightingUniformsGroup ],
-        name: 'Box',
-        defines: {
-            POINTLIGHTS_MAX: pointLightsMax,
-        },
-        vertexShader: document.getElementById('vertexShader').textContent,
-        fragmentShader: document.getElementById('fragmentShader').textContent,
-        glslVersion: THREE.GLSL3,
-    });
-
-    const plane = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), material.clone());
-    plane.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
-    plane.material.uniforms.modelMatrix.value = plane.matrixWorld;
-    plane.material.uniforms.normalMatrix.value = plane.normalMatrix;
-    plane.rotation.x = -Math.PI / 2;
-    plane.position.y = -1;
-    scene.add(plane);
-
-    // meshes
-    const gridSize = { x: 10, y: 1, z: 10 };
-    const spacing = 6;
-
-    for (let i = 0; i < gridSize.x; i++) {
-        for (let j = 0; j < gridSize.y; j++) {
-            for (let k = 0; k < gridSize.z; k++) {
-                const mesh = new THREE.Mesh(geometry, material.clone());
-                mesh.name = 'Sphere';
-                mesh.material.uniformsGroups = [cameraUniformsGroup, lightingUniformsGroup];
-                mesh.material.uniforms.modelMatrix.value = mesh.matrixWorld;
-                mesh.material.uniforms.normalMatrix.value = mesh.normalMatrix;
-                scene.add(mesh);
-
-                mesh.position.x = i * spacing - (gridSize.x * spacing) / 2;
-                mesh.position.y = 0;
-                mesh.position.z = k * spacing - (gridSize.z * spacing) / 2;
-            }
-        }
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize, false);
-
-    // controls
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enablePan = false;
-
-    // stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // gui
-    const gui = new GUI();
-    gui.add(api, 'count', 1, pointLightsMax)
-        .step(1)
-        .onChange(function () {
-            lightingUniformsGroup.uniforms[2].value = api.count;
-        });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const elapsedTime = clock.getElapsedTime();
-
-    const lights = lightingUniformsGroup.uniforms[0];
-
-    // Parameters for circular movement
-    const radius = 5; // Smaller radius for individual circular movements
-    const speed = 0.5; // Speed of rotation
-
-    // Update each light's position
-    for (let i = 0; i < lights.length; i++) {
-        const light = lights[i];
-        const center = lightCenters[i];
-
-        // Calculate circular movement around the light's center
-        const angle = speed * elapsedTime + i * 0.5; // Phase difference for each light
-        const x = center.x + Math.sin(angle) * radius;
-        const z = center.z + Math.cos(angle) * radius;
-
-        // Update the light's position
-        light.value.set(x, 1, z, 0);
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgl_video_kinect.ts b/examples-testing/examples/webgl_video_kinect.ts
deleted file mode 100644
index 4f0e2f113..000000000
--- a/examples-testing/examples/webgl_video_kinect.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, camera, renderer;
-let geometry, mesh, material;
-let mouse, center;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    const info = document.createElement('div');
-    info.id = 'info';
-    info.innerHTML = '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - kinect';
-    document.body.appendChild(info);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.set(0, 0, 500);
-
-    scene = new THREE.Scene();
-    center = new THREE.Vector3();
-    center.z = -1000;
-
-    const video = document.getElementById('video');
-
-    const texture = new THREE.VideoTexture(video);
-    texture.minFilter = THREE.NearestFilter;
-
-    const width = 640,
-        height = 480;
-    const nearClipping = 850,
-        farClipping = 4000;
-
-    geometry = new THREE.BufferGeometry();
-
-    const vertices = new Float32Array(width * height * 3);
-
-    for (let i = 0, j = 0, l = vertices.length; i < l; i += 3, j++) {
-        vertices[i] = j % width;
-        vertices[i + 1] = Math.floor(j / width);
-    }
-
-    geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
-
-    material = new THREE.ShaderMaterial({
-        uniforms: {
-            map: { value: texture },
-            width: { value: width },
-            height: { value: height },
-            nearClipping: { value: nearClipping },
-            farClipping: { value: farClipping },
-
-            pointSize: { value: 2 },
-            zOffset: { value: 1000 },
-        },
-        vertexShader: document.getElementById('vs').textContent,
-        fragmentShader: document.getElementById('fs').textContent,
-        blending: THREE.AdditiveBlending,
-        depthTest: false,
-        depthWrite: false,
-        transparent: true,
-    });
-
-    mesh = new THREE.Points(geometry, material);
-    scene.add(mesh);
-
-    const gui = new GUI();
-    gui.add(material.uniforms.nearClipping, 'value', 1, 10000, 1.0).name('nearClipping');
-    gui.add(material.uniforms.farClipping, 'value', 1, 10000, 1.0).name('farClipping');
-    gui.add(material.uniforms.pointSize, 'value', 1, 10, 1.0).name('pointSize');
-    gui.add(material.uniforms.zOffset, 'value', 0, 4000, 1.0).name('zOffset');
-
-    video.play();
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    mouse = new THREE.Vector3(0, 0, 1);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouse.x = (event.clientX - window.innerWidth / 2) * 8;
-    mouse.y = (event.clientY - window.innerHeight / 2) * 8;
-}
-
-function animate() {
-    camera.position.x += (mouse.x - camera.position.x) * 0.05;
-    camera.position.y += (-mouse.y - camera.position.y) * 0.05;
-    camera.lookAt(center);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_video_panorama_equirectangular.ts b/examples-testing/examples/webgl_video_panorama_equirectangular.ts
deleted file mode 100644
index 866eca16a..000000000
--- a/examples-testing/examples/webgl_video_panorama_equirectangular.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-
-let isUserInteracting = false,
-    lon = 0,
-    lat = 0,
-    phi = 0,
-    theta = 0,
-    onPointerDownPointerX = 0,
-    onPointerDownPointerY = 0,
-    onPointerDownLon = 0,
-    onPointerDownLat = 0;
-
-const distance = 0.5;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.25, 10);
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.SphereGeometry(5, 60, 40);
-    // invert the geometry on the x-axis so that all of the faces point inward
-    geometry.scale(-1, 1, 1);
-
-    const video = document.getElementById('video');
-    video.play();
-
-    const texture = new THREE.VideoTexture(video);
-    texture.colorSpace = THREE.SRGBColorSpace;
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    document.addEventListener('pointerdown', onPointerDown);
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerDown(event) {
-    isUserInteracting = true;
-
-    onPointerDownPointerX = event.clientX;
-    onPointerDownPointerY = event.clientY;
-
-    onPointerDownLon = lon;
-    onPointerDownLat = lat;
-}
-
-function onPointerMove(event) {
-    if (isUserInteracting === true) {
-        lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;
-        lat = (onPointerDownPointerY - event.clientY) * 0.1 + onPointerDownLat;
-    }
-}
-
-function onPointerUp() {
-    isUserInteracting = false;
-}
-
-function animate() {
-    lat = Math.max(-85, Math.min(85, lat));
-    phi = THREE.MathUtils.degToRad(90 - lat);
-    theta = THREE.MathUtils.degToRad(lon);
-
-    camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
-    camera.position.y = distance * Math.cos(phi);
-    camera.position.z = distance * Math.sin(phi) * Math.sin(theta);
-
-    camera.lookAt(0, 0, 0);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_volume_cloud.ts b/examples-testing/examples/webgl_volume_cloud.ts
deleted file mode 100644
index 77dd8de43..000000000
--- a/examples-testing/examples/webgl_volume_cloud.ts
+++ /dev/null
@@ -1,279 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let renderer, scene, camera;
-let mesh;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 1.5);
-
-    new OrbitControls(camera, renderer.domElement);
-
-    // Sky
-
-    const canvas = document.createElement('canvas');
-    canvas.width = 1;
-    canvas.height = 32;
-
-    const context = canvas.getContext('2d');
-    const gradient = context.createLinearGradient(0, 0, 0, 32);
-    gradient.addColorStop(0.0, '#014a84');
-    gradient.addColorStop(0.5, '#0561a0');
-    gradient.addColorStop(1.0, '#437ab6');
-    context.fillStyle = gradient;
-    context.fillRect(0, 0, 1, 32);
-
-    const skyMap = new THREE.CanvasTexture(canvas);
-    skyMap.colorSpace = THREE.SRGBColorSpace;
-
-    const sky = new THREE.Mesh(
-        new THREE.SphereGeometry(10),
-        new THREE.MeshBasicMaterial({ map: skyMap, side: THREE.BackSide }),
-    );
-    scene.add(sky);
-
-    // Texture
-
-    const size = 128;
-    const data = new Uint8Array(size * size * size);
-
-    let i = 0;
-    const scale = 0.05;
-    const perlin = new ImprovedNoise();
-    const vector = new THREE.Vector3();
-
-    for (let z = 0; z < size; z++) {
-        for (let y = 0; y < size; y++) {
-            for (let x = 0; x < size; x++) {
-                const d =
-                    1.0 -
-                    vector
-                        .set(x, y, z)
-                        .subScalar(size / 2)
-                        .divideScalar(size)
-                        .length();
-                data[i] = (128 + 128 * perlin.noise((x * scale) / 1.5, y * scale, (z * scale) / 1.5)) * d * d;
-                i++;
-            }
-        }
-    }
-
-    const texture = new THREE.Data3DTexture(data, size, size, size);
-    texture.format = THREE.RedFormat;
-    texture.minFilter = THREE.LinearFilter;
-    texture.magFilter = THREE.LinearFilter;
-    texture.unpackAlignment = 1;
-    texture.needsUpdate = true;
-
-    // Material
-
-    const vertexShader = /* glsl */ `
-					in vec3 position;
-
-					uniform mat4 modelMatrix;
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-					uniform vec3 cameraPos;
-
-					out vec3 vOrigin;
-					out vec3 vDirection;
-
-					void main() {
-						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
-
-						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
-						vDirection = position - vOrigin;
-
-						gl_Position = projectionMatrix * mvPosition;
-					}
-				`;
-
-    const fragmentShader = /* glsl */ `
-					precision highp float;
-					precision highp sampler3D;
-
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-
-					in vec3 vOrigin;
-					in vec3 vDirection;
-
-					out vec4 color;
-
-					uniform vec3 base;
-					uniform sampler3D map;
-
-					uniform float threshold;
-					uniform float range;
-					uniform float opacity;
-					uniform float steps;
-					uniform float frame;
-
-					uint wang_hash(uint seed)
-					{
-							seed = (seed ^ 61u) ^ (seed >> 16u);
-							seed *= 9u;
-							seed = seed ^ (seed >> 4u);
-							seed *= 0x27d4eb2du;
-							seed = seed ^ (seed >> 15u);
-							return seed;
-					}
-
-					float randomFloat(inout uint seed)
-					{
-							return float(wang_hash(seed)) / 4294967296.;
-					}
-
-					vec2 hitBox( vec3 orig, vec3 dir ) {
-						const vec3 box_min = vec3( - 0.5 );
-						const vec3 box_max = vec3( 0.5 );
-						vec3 inv_dir = 1.0 / dir;
-						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
-						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
-						vec3 tmin = min( tmin_tmp, tmax_tmp );
-						vec3 tmax = max( tmin_tmp, tmax_tmp );
-						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
-						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
-						return vec2( t0, t1 );
-					}
-
-					float sample1( vec3 p ) {
-						return texture( map, p ).r;
-					}
-
-					float shading( vec3 coord ) {
-						float step = 0.01;
-						return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
-					}
-
-					vec4 linearToSRGB( in vec4 value ) {
-						return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
-					}
-
-					void main(){
-						vec3 rayDir = normalize( vDirection );
-						vec2 bounds = hitBox( vOrigin, rayDir );
-
-						if ( bounds.x > bounds.y ) discard;
-
-						bounds.x = max( bounds.x, 0.0 );
-
-						vec3 p = vOrigin + bounds.x * rayDir;
-						vec3 inc = 1.0 / abs( rayDir );
-						float delta = min( inc.x, min( inc.y, inc.z ) );
-						delta /= steps;
-
-						// Jitter
-
-						// Nice little seed from
-						// https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
-						uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
-						vec3 size = vec3( textureSize( map, 0 ) );
-						float randNum = randomFloat( seed ) * 2.0 - 1.0;
-						p += rayDir * randNum * ( 1.0 / size );
-
-						//
-
-						vec4 ac = vec4( base, 0.0 );
-
-						for ( float t = bounds.x; t < bounds.y; t += delta ) {
-
-							float d = sample1( p + 0.5 );
-
-							d = smoothstep( threshold - range, threshold + range, d ) * opacity;
-
-							float col = shading( p + 0.5 ) * 3.0 + ( ( p.x + p.y ) * 0.25 ) + 0.2;
-
-							ac.rgb += ( 1.0 - ac.a ) * d * col;
-
-							ac.a += ( 1.0 - ac.a ) * d;
-
-							if ( ac.a >= 0.95 ) break;
-
-							p += rayDir * delta;
-
-						}
-
-						color = linearToSRGB( ac );
-
-						if ( color.a == 0.0 ) discard;
-
-					}
-				`;
-
-    const geometry = new THREE.BoxGeometry(1, 1, 1);
-    const material = new THREE.RawShaderMaterial({
-        glslVersion: THREE.GLSL3,
-        uniforms: {
-            base: { value: new THREE.Color(0x798aa0) },
-            map: { value: texture },
-            cameraPos: { value: new THREE.Vector3() },
-            threshold: { value: 0.25 },
-            opacity: { value: 0.25 },
-            range: { value: 0.1 },
-            steps: { value: 100 },
-            frame: { value: 0 },
-        },
-        vertexShader,
-        fragmentShader,
-        side: THREE.BackSide,
-        transparent: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const parameters = {
-        threshold: 0.25,
-        opacity: 0.25,
-        range: 0.1,
-        steps: 100,
-    };
-
-    function update() {
-        material.uniforms.threshold.value = parameters.threshold;
-        material.uniforms.opacity.value = parameters.opacity;
-        material.uniforms.range.value = parameters.range;
-        material.uniforms.steps.value = parameters.steps;
-    }
-
-    const gui = new GUI();
-    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'opacity', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'range', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'steps', 0, 200, 1).onChange(update);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.material.uniforms.cameraPos.value.copy(camera.position);
-    mesh.rotation.y = -performance.now() / 7500;
-
-    mesh.material.uniforms.frame.value++;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_volume_instancing.ts b/examples-testing/examples/webgl_volume_instancing.ts
deleted file mode 100644
index bf90eeea9..000000000
--- a/examples-testing/examples/webgl_volume_instancing.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { VOXLoader, VOXData3DTexture } from 'three/addons/loaders/VOXLoader.js';
-
-let renderer, scene, camera, controls, clock;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
-    camera.position.set(0, 0, 4);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -1.0;
-    controls.enableDamping = true;
-
-    clock = new THREE.Clock();
-
-    // Material
-
-    const vertexShader = /* glsl */ `
-					in vec3 position;
-					in mat4 instanceMatrix;
-
-					uniform mat4 modelMatrix;
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-					uniform vec3 cameraPos;
-
-					out vec3 vOrigin;
-					out vec3 vDirection;
-
-					void main() {
-						vec4 mvPosition = modelViewMatrix * instanceMatrix * vec4( position, 1.0 );
-
-						vOrigin = vec3( inverse( instanceMatrix * modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
-						vDirection = position - vOrigin;
-
-						gl_Position = projectionMatrix * mvPosition;
-					}
-				`;
-
-    const fragmentShader = /* glsl */ `
-					precision highp float;
-					precision highp sampler3D;
-
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-
-					in vec3 vOrigin;
-					in vec3 vDirection;
-
-					out vec4 color;
-
-					uniform sampler3D map;
-
-					uniform float threshold;
-					uniform float steps;
-
-					vec2 hitBox( vec3 orig, vec3 dir ) {
-						const vec3 box_min = vec3( - 0.5 );
-						const vec3 box_max = vec3( 0.5 );
-						vec3 inv_dir = 1.0 / dir;
-						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
-						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
-						vec3 tmin = min( tmin_tmp, tmax_tmp );
-						vec3 tmax = max( tmin_tmp, tmax_tmp );
-						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
-						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
-						return vec2( t0, t1 );
-					}
-
-					float sample1( vec3 p ) {
-						return texture( map, p ).r;
-					}
-
-					#define epsilon .0001
-
-					vec3 normal( vec3 coord ) {
-						if ( coord.x < epsilon ) return vec3( 1.0, 0.0, 0.0 );
-						if ( coord.y < epsilon ) return vec3( 0.0, 1.0, 0.0 );
-						if ( coord.z < epsilon ) return vec3( 0.0, 0.0, 1.0 );
-						if ( coord.x > 1.0 - epsilon ) return vec3( - 1.0, 0.0, 0.0 );
-						if ( coord.y > 1.0 - epsilon ) return vec3( 0.0, - 1.0, 0.0 );
-						if ( coord.z > 1.0 - epsilon ) return vec3( 0.0, 0.0, - 1.0 );
-
-						float step = 0.01;
-						float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
-						float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
-						float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );
-
-						return normalize( vec3( x, y, z ) );
-					}
-
-					void main(){
-
-						vec3 rayDir = normalize( vDirection );
-						vec2 bounds = hitBox( vOrigin, rayDir );
-
-						if ( bounds.x > bounds.y ) discard;
-
-						bounds.x = max( bounds.x, 0.0 );
-
-						vec3 p = vOrigin + bounds.x * rayDir;
-						vec3 inc = 1.0 / abs( rayDir );
-						float delta = min( inc.x, min( inc.y, inc.z ) );
-						delta /= 50.0;
-
-						for ( float t = bounds.x; t < bounds.y; t += delta ) {
-
-							float d = sample1( p + 0.5 );
-
-							if ( d > 0.5 ) {
-
-								color.rgb = p * 2.0; // normal( p + 0.5 ); // * 0.5 + ( p * 1.5 + 0.25 );
-								color.a = 1.;
-								break;
-
-							}
-
-							p += rayDir * delta;
-
-						}
-
-						if ( color.a == 0.0 ) discard;
-
-					}
-				`;
-
-    const loader = new VOXLoader();
-    loader.load('models/vox/menger.vox', function (chunks) {
-        for (let i = 0; i < chunks.length; i++) {
-            const chunk = chunks[i];
-
-            const geometry = new THREE.BoxGeometry(1, 1, 1);
-            const material = new THREE.RawShaderMaterial({
-                glslVersion: THREE.GLSL3,
-                uniforms: {
-                    map: { value: new VOXData3DTexture(chunk) },
-                    cameraPos: { value: new THREE.Vector3() },
-                },
-                vertexShader,
-                fragmentShader,
-                side: THREE.BackSide,
-            });
-
-            const mesh = new THREE.InstancedMesh(geometry, material, 50000);
-            mesh.onBeforeRender = function () {
-                this.material.uniforms.cameraPos.value.copy(camera.position);
-            };
-
-            const transform = new THREE.Object3D();
-
-            for (let i = 0; i < mesh.count; i++) {
-                transform.position.random().subScalar(0.5).multiplyScalar(150);
-                transform.rotation.x = Math.random() * Math.PI;
-                transform.rotation.y = Math.random() * Math.PI;
-                transform.rotation.z = Math.random() * Math.PI;
-                transform.updateMatrix();
-
-                mesh.setMatrixAt(i, transform.matrix);
-            }
-
-            scene.add(mesh);
-        }
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-    controls.update(delta);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_volume_perlin.ts b/examples-testing/examples/webgl_volume_perlin.ts
deleted file mode 100644
index a98f9a682..000000000
--- a/examples-testing/examples/webgl_volume_perlin.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let renderer, scene, camera;
-let mesh;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 2);
-
-    new OrbitControls(camera, renderer.domElement);
-
-    // Texture
-
-    const size = 128;
-    const data = new Uint8Array(size * size * size);
-
-    let i = 0;
-    const perlin = new ImprovedNoise();
-    const vector = new THREE.Vector3();
-
-    for (let z = 0; z < size; z++) {
-        for (let y = 0; y < size; y++) {
-            for (let x = 0; x < size; x++) {
-                vector.set(x, y, z).divideScalar(size);
-
-                const d = perlin.noise(vector.x * 6.5, vector.y * 6.5, vector.z * 6.5);
-
-                data[i++] = d * 128 + 128;
-            }
-        }
-    }
-
-    const texture = new THREE.Data3DTexture(data, size, size, size);
-    texture.format = THREE.RedFormat;
-    texture.minFilter = THREE.LinearFilter;
-    texture.magFilter = THREE.LinearFilter;
-    texture.unpackAlignment = 1;
-    texture.needsUpdate = true;
-
-    // Material
-
-    const vertexShader = /* glsl */ `
-					in vec3 position;
-
-					uniform mat4 modelMatrix;
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-					uniform vec3 cameraPos;
-
-					out vec3 vOrigin;
-					out vec3 vDirection;
-
-					void main() {
-						vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
-
-						vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
-						vDirection = position - vOrigin;
-
-						gl_Position = projectionMatrix * mvPosition;
-					}
-				`;
-
-    const fragmentShader = /* glsl */ `
-					precision highp float;
-					precision highp sampler3D;
-
-					uniform mat4 modelViewMatrix;
-					uniform mat4 projectionMatrix;
-
-					in vec3 vOrigin;
-					in vec3 vDirection;
-
-					out vec4 color;
-
-					uniform sampler3D map;
-
-					uniform float threshold;
-					uniform float steps;
-
-					vec2 hitBox( vec3 orig, vec3 dir ) {
-						const vec3 box_min = vec3( - 0.5 );
-						const vec3 box_max = vec3( 0.5 );
-						vec3 inv_dir = 1.0 / dir;
-						vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
-						vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
-						vec3 tmin = min( tmin_tmp, tmax_tmp );
-						vec3 tmax = max( tmin_tmp, tmax_tmp );
-						float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
-						float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
-						return vec2( t0, t1 );
-					}
-
-					float sample1( vec3 p ) {
-						return texture( map, p ).r;
-					}
-
-					#define epsilon .0001
-
-					vec3 normal( vec3 coord ) {
-						if ( coord.x < epsilon ) return vec3( 1.0, 0.0, 0.0 );
-						if ( coord.y < epsilon ) return vec3( 0.0, 1.0, 0.0 );
-						if ( coord.z < epsilon ) return vec3( 0.0, 0.0, 1.0 );
-						if ( coord.x > 1.0 - epsilon ) return vec3( - 1.0, 0.0, 0.0 );
-						if ( coord.y > 1.0 - epsilon ) return vec3( 0.0, - 1.0, 0.0 );
-						if ( coord.z > 1.0 - epsilon ) return vec3( 0.0, 0.0, - 1.0 );
-
-						float step = 0.01;
-						float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
-						float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
-						float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );
-
-						return normalize( vec3( x, y, z ) );
-					}
-
-					void main(){
-
-						vec3 rayDir = normalize( vDirection );
-						vec2 bounds = hitBox( vOrigin, rayDir );
-
-						if ( bounds.x > bounds.y ) discard;
-
-						bounds.x = max( bounds.x, 0.0 );
-
-						vec3 p = vOrigin + bounds.x * rayDir;
-						vec3 inc = 1.0 / abs( rayDir );
-						float delta = min( inc.x, min( inc.y, inc.z ) );
-						delta /= steps;
-
-						for ( float t = bounds.x; t < bounds.y; t += delta ) {
-
-							float d = sample1( p + 0.5 );
-
-							if ( d > threshold ) {
-
-								color.rgb = normal( p + 0.5 ) * 0.5 + ( p * 1.5 + 0.25 );
-								color.a = 1.;
-								break;
-
-							}
-
-							p += rayDir * delta;
-
-						}
-
-						if ( color.a == 0.0 ) discard;
-
-					}
-				`;
-
-    const geometry = new THREE.BoxGeometry(1, 1, 1);
-    const material = new THREE.RawShaderMaterial({
-        glslVersion: THREE.GLSL3,
-        uniforms: {
-            map: { value: texture },
-            cameraPos: { value: new THREE.Vector3() },
-            threshold: { value: 0.6 },
-            steps: { value: 200 },
-        },
-        vertexShader,
-        fragmentShader,
-        side: THREE.BackSide,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const parameters = { threshold: 0.6, steps: 200 };
-
-    function update() {
-        material.uniforms.threshold.value = parameters.threshold;
-        material.uniforms.steps.value = parameters.steps;
-    }
-
-    const gui = new GUI();
-    gui.add(parameters, 'threshold', 0, 1, 0.01).onChange(update);
-    gui.add(parameters, 'steps', 0, 300, 1).onChange(update);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    mesh.material.uniforms.cameraPos.value.copy(camera.position);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_water.ts b/examples-testing/examples/webgl_water.ts
deleted file mode 100644
index 496a5f855..000000000
--- a/examples-testing/examples/webgl_water.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Water } from 'three/addons/objects/Water2.js';
-
-let scene, camera, clock, renderer, water;
-
-let torusKnot;
-
-const params = {
-    color: '#ffffff',
-    scale: 4,
-    flowX: 1,
-    flowY: 1,
-};
-
-init();
-
-function init() {
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
-    camera.position.set(-15, 7, 15);
-    camera.lookAt(scene.position);
-
-    // clock
-
-    clock = new THREE.Clock();
-
-    // mesh
-
-    const torusKnotGeometry = new THREE.TorusKnotGeometry(3, 1, 256, 32);
-    const torusKnotMaterial = new THREE.MeshNormalMaterial();
-
-    torusKnot = new THREE.Mesh(torusKnotGeometry, torusKnotMaterial);
-    torusKnot.position.y = 4;
-    torusKnot.scale.set(0.5, 0.5, 0.5);
-    scene.add(torusKnot);
-
-    // ground
-
-    const groundGeometry = new THREE.PlaneGeometry(20, 20);
-    const groundMaterial = new THREE.MeshStandardMaterial({ roughness: 0.8, metalness: 0.4 });
-    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
-    ground.rotation.x = Math.PI * -0.5;
-    scene.add(ground);
-
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 16;
-        map.repeat.set(4, 4);
-        map.colorSpace = THREE.SRGBColorSpace;
-        groundMaterial.map = map;
-        groundMaterial.needsUpdate = true;
-    });
-
-    // water
-
-    const waterGeometry = new THREE.PlaneGeometry(20, 20);
-
-    water = new Water(waterGeometry, {
-        color: params.color,
-        scale: params.scale,
-        flowDirection: new THREE.Vector2(params.flowX, params.flowY),
-        textureWidth: 1024,
-        textureHeight: 1024,
-    });
-
-    water.position.y = 1;
-    water.rotation.x = Math.PI * -0.5;
-    scene.add(water);
-
-    // skybox
-
-    const cubeTextureLoader = new THREE.CubeTextureLoader();
-    cubeTextureLoader.setPath('textures/cube/Park2/');
-
-    const cubeTexture = cubeTextureLoader.load([
-        'posx.jpg',
-        'negx.jpg',
-        'posy.jpg',
-        'negy.jpg',
-        'posz.jpg',
-        'negz.jpg',
-    ]);
-
-    scene.background = cubeTexture;
-
-    // light
-
-    const ambientLight = new THREE.AmbientLight(0xe7e7e7, 1.2);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
-    directionalLight.position.set(-1, 1, 1);
-    scene.add(directionalLight);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // gui
-
-    const gui = new GUI();
-
-    gui.addColor(params, 'color').onChange(function (value) {
-        water.material.uniforms['color'].value.set(value);
-    });
-    gui.add(params, 'scale', 1, 10).onChange(function (value) {
-        water.material.uniforms['config'].value.w = value;
-    });
-    gui.add(params, 'flowX', -1, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            water.material.uniforms['flowDirection'].value.x = value;
-            water.material.uniforms['flowDirection'].value.normalize();
-        });
-    gui.add(params, 'flowY', -1, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            water.material.uniforms['flowDirection'].value.y = value;
-            water.material.uniforms['flowDirection'].value.normalize();
-        });
-
-    gui.open();
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 5;
-    controls.maxDistance = 50;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    torusKnot.rotation.x += delta;
-    torusKnot.rotation.y += delta * 0.5;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgl_water_flowmap.ts b/examples-testing/examples/webgl_water_flowmap.ts
deleted file mode 100644
index d0255e431..000000000
--- a/examples-testing/examples/webgl_water_flowmap.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Water } from 'three/addons/objects/Water2.js';
-
-let scene, camera, renderer, water;
-
-init();
-
-function init() {
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
-    camera.position.set(0, 25, 0);
-    camera.lookAt(scene.position);
-
-    // ground
-
-    const groundGeometry = new THREE.PlaneGeometry(20, 20, 10, 10);
-    const groundMaterial = new THREE.MeshBasicMaterial({ color: 0xe7e7e7 });
-    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
-    ground.rotation.x = Math.PI * -0.5;
-    scene.add(ground);
-
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 16;
-        map.repeat.set(4, 4);
-        map.colorSpace = THREE.SRGBColorSpace;
-        groundMaterial.map = map;
-        groundMaterial.needsUpdate = true;
-    });
-
-    // water
-
-    const waterGeometry = new THREE.PlaneGeometry(20, 20);
-    const flowMap = textureLoader.load('textures/water/Water_1_M_Flow.jpg');
-
-    water = new Water(waterGeometry, {
-        scale: 2,
-        textureWidth: 1024,
-        textureHeight: 1024,
-        flowMap: flowMap,
-    });
-
-    water.position.y = 1;
-    water.rotation.x = Math.PI * -0.5;
-    scene.add(water);
-
-    // flow map helper
-
-    const helperGeometry = new THREE.PlaneGeometry(20, 20);
-    const helperMaterial = new THREE.MeshBasicMaterial({ map: flowMap });
-    const helper = new THREE.Mesh(helperGeometry, helperMaterial);
-    helper.position.y = 1.01;
-    helper.rotation.x = Math.PI * -0.5;
-    helper.visible = false;
-    scene.add(helper);
-
-    // renderer
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const gui = new GUI();
-    gui.add(helper, 'visible').name('Show Flow Map');
-    gui.open();
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 5;
-    controls.maxDistance = 50;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_backdrop_area.ts b/examples-testing/examples/webgpu_backdrop_area.ts
deleted file mode 100644
index 97c224cea..000000000
--- a/examples-testing/examples/webgpu_backdrop_area.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import * as THREE from 'three';
-import {
-    color,
-    linearDepth,
-    viewportLinearDepth,
-    viewportSharedTexture,
-    textureBicubic,
-    viewportMipTexture,
-    viewportUV,
-    checker,
-    uv,
-    modelScale,
-} from 'three/tsl';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-let mixer, clock;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 25);
-    camera.position.set(3, 2, 3);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x777777);
-    camera.lookAt(0, 1, 0);
-
-    clock = new THREE.Clock();
-
-    const light = new THREE.PointLight(0xffffff, 50);
-    camera.add(light);
-    scene.add(camera);
-
-    const ambient = new THREE.AmbientLight(0x4466ff, 1);
-    scene.add(ambient);
-
-    // model
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/Michelle.glb', function (gltf) {
-        const object = gltf.scene;
-        mixer = new THREE.AnimationMixer(object);
-
-        const action = mixer.clipAction(gltf.animations[0]);
-        action.play();
-
-        scene.add(object);
-    });
-
-    // volume
-
-    // compare depth from viewportLinearDepth with linearDepth() to create a distance field
-    // viewportLinearDepth return the linear depth of the scene
-    // linearDepth() returns the linear depth of the mesh
-    const depthDistance = viewportLinearDepth.distance(linearDepth());
-
-    const depthAlphaNode = depthDistance.oneMinus().smoothstep(0.9, 2).mul(10).saturate();
-    const depthBlurred = textureBicubic(
-        viewportMipTexture(),
-        depthDistance
-            .smoothstep(0, 0.6)
-            .mul(40 * 5)
-            .clamp(0, 5),
-    );
-
-    const blurredBlur = new THREE.MeshBasicNodeMaterial();
-    blurredBlur.backdropNode = depthBlurred.add(depthAlphaNode.mix(color(0x0066ff), 0));
-    blurredBlur.transparent = true;
-    blurredBlur.side = THREE.DoubleSide;
-
-    const volumeMaterial = new THREE.MeshBasicNodeMaterial();
-    volumeMaterial.colorNode = color(0x0066ff);
-    volumeMaterial.backdropNode = viewportSharedTexture();
-    volumeMaterial.backdropAlphaNode = depthAlphaNode;
-    volumeMaterial.transparent = true;
-    volumeMaterial.side = THREE.DoubleSide;
-
-    const depthMaterial = new THREE.MeshBasicNodeMaterial();
-    depthMaterial.backdropNode = depthAlphaNode;
-    depthMaterial.transparent = true;
-    depthMaterial.side = THREE.DoubleSide;
-
-    const bicubicMaterial = new THREE.MeshBasicNodeMaterial();
-    bicubicMaterial.backdropNode = textureBicubic(viewportMipTexture(), 5); // @TODO: Move to alpha value [ 0, 1 ]
-    bicubicMaterial.backdropAlphaNode = checker(uv().mul(3).mul(modelScale.xy));
-    bicubicMaterial.opacityNode = bicubicMaterial.backdropAlphaNode;
-    bicubicMaterial.transparent = true;
-    bicubicMaterial.side = THREE.DoubleSide;
-
-    const pixelMaterial = new THREE.MeshBasicNodeMaterial();
-    pixelMaterial.backdropNode = viewportSharedTexture(viewportUV.mul(100).floor().div(100));
-    pixelMaterial.transparent = true;
-
-    // box / floor
-
-    const box = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), volumeMaterial);
-    box.position.set(0, 1, 0);
-    scene.add(box);
-
-    const floor = new THREE.Mesh(
-        new THREE.BoxGeometry(1.99, 0.01, 1.99),
-        new THREE.MeshBasicNodeMaterial({ color: 0x333333 }),
-    );
-    floor.position.set(0, 0, 0);
-    scene.add(floor);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer(/*{ antialias: true }*/);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.LinearToneMapping;
-    renderer.toneMappingExposure = 0.2;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 1, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-
-    // gui
-
-    const materials = {
-        blurred: blurredBlur,
-        volume: volumeMaterial,
-        depth: depthMaterial,
-        bicubic: bicubicMaterial,
-        pixel: pixelMaterial,
-    };
-
-    const gui = new GUI();
-    const options = { material: 'blurred' };
-
-    box.material = materials[options.material];
-
-    gui.add(box.scale, 'x', 0.1, 2, 0.01);
-    gui.add(box.scale, 'z', 0.1, 2, 0.01);
-    gui.add(options, 'material', Object.keys(materials)).onChange(name => {
-        box.material = materials[name];
-    });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) mixer.update(delta);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts b/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
deleted file mode 100644
index 155276322..000000000
--- a/examples-testing/examples/webgpu_camera_logarithmicdepthbuffer.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import * as THREE from 'three';
-
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-// 1 micrometer to 100 billion light years in one scene, with 1 unit = 1 meter?  preposterous!  and yet...
-const NEAR = 1e-6,
-    FAR = 1e27;
-let SCREEN_WIDTH = window.innerWidth;
-let SCREEN_HEIGHT = window.innerHeight;
-let screensplit = 0.25,
-    screensplit_right = 0;
-const mouse = [0.5, 0.5];
-let zoompos = -100,
-    minzoomspeed = 0.015;
-let zoomspeed = minzoomspeed;
-
-let container, border, stats;
-const objects = {};
-
-// Generate a number of text labels, from 1µm in size up to 100,000,000 light years
-// Try to use some descriptive real-world examples of objects at each scale
-
-const labeldata = [
-    { size: 0.01, scale: 0.0001, label: 'microscopic (1µm)' }, // FIXME - triangulating text fails at this size, so we scale instead
-    { size: 0.01, scale: 0.1, label: 'minuscule (1mm)' },
-    { size: 0.01, scale: 1.0, label: 'tiny (1cm)' },
-    { size: 1, scale: 1.0, label: 'child-sized (1m)' },
-    { size: 10, scale: 1.0, label: 'tree-sized (10m)' },
-    { size: 100, scale: 1.0, label: 'building-sized (100m)' },
-    { size: 1000, scale: 1.0, label: 'medium (1km)' },
-    { size: 10000, scale: 1.0, label: 'city-sized (10km)' },
-    { size: 3400000, scale: 1.0, label: 'moon-sized (3,400 Km)' },
-    { size: 12000000, scale: 1.0, label: 'planet-sized (12,000 km)' },
-    { size: 1400000000, scale: 1.0, label: 'sun-sized (1,400,000 km)' },
-    { size: 7.47e12, scale: 1.0, label: 'solar system-sized (50Au)' },
-    { size: 9.4605284e15, scale: 1.0, label: 'gargantuan (1 light year)' },
-    { size: 3.08567758e16, scale: 1.0, label: 'ludicrous (1 parsec)' },
-    { size: 1e19, scale: 1.0, label: 'mind boggling (1000 light years)' },
-];
-
-init().then(animate);
-
-async function init() {
-    container = document.getElementById('container');
-
-    const loader = new FontLoader();
-    const font = await loader.loadAsync('fonts/helvetiker_regular.typeface.json');
-
-    const scene = initScene(font);
-
-    // Initialize two copies of the same scene, one with normal z-buffer and one with logarithmic z-buffer
-    objects.normal = await initView(scene, 'normal', false);
-    objects.logzbuf = await initView(scene, 'logzbuf', true);
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
-    border = document.getElementById('renderer_border');
-    border.addEventListener('pointerdown', onBorderPointerDown);
-
-    window.addEventListener('mousemove', onMouseMove);
-    window.addEventListener('resize', onWindowResize);
-    window.addEventListener('wheel', onMouseWheel);
-}
-
-async function initView(scene, name, logDepthBuf) {
-    const framecontainer = document.getElementById('container_' + name);
-
-    const camera = new THREE.PerspectiveCamera(50, (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT, NEAR, FAR);
-    scene.add(camera);
-
-    const renderer = new THREE.WebGPURenderer({ antialias: true, logarithmicDepthBuffer: logDepthBuf });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH / 2, SCREEN_HEIGHT);
-    renderer.domElement.style.position = 'relative';
-    renderer.domElement.id = 'renderer_' + name;
-    framecontainer.appendChild(renderer.domElement);
-
-    await renderer.init();
-
-    return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
-}
-
-function initScene(font) {
-    const scene = new THREE.Scene();
-
-    scene.add(new THREE.AmbientLight(0x777777));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(100, 100, 100);
-    scene.add(light);
-
-    const materialargs = {
-        color: 0xffffff,
-        specular: 0x050505,
-        shininess: 50,
-        emissive: 0x000000,
-    };
-
-    const geometry = new THREE.SphereGeometry(0.5, 24, 12);
-
-    for (let i = 0; i < labeldata.length; i++) {
-        const scale = labeldata[i].scale || 1;
-
-        const labelgeo = new TextGeometry(labeldata[i].label, {
-            font: font,
-            size: labeldata[i].size,
-            depth: labeldata[i].size / 2,
-        });
-
-        labelgeo.computeBoundingSphere();
-
-        // center text
-        labelgeo.translate(-labelgeo.boundingSphere.radius, 0, 0);
-
-        materialargs.color = new THREE.Color().setHSL(Math.random(), 0.5, 0.5);
-
-        const material = new THREE.MeshPhongMaterial(materialargs);
-
-        const group = new THREE.Group();
-        group.position.z = -labeldata[i].size * scale;
-        scene.add(group);
-
-        const textmesh = new THREE.Mesh(labelgeo, material);
-        textmesh.scale.set(scale, scale, scale);
-        textmesh.position.z = -labeldata[i].size * scale;
-        textmesh.position.y = (labeldata[i].size / 4) * scale;
-        group.add(textmesh);
-
-        const dotmesh = new THREE.Mesh(geometry, material);
-        dotmesh.position.y = (-labeldata[i].size / 4) * scale;
-        dotmesh.scale.multiplyScalar(labeldata[i].size * scale);
-        group.add(dotmesh);
-    }
-
-    return scene;
-}
-
-function updateRendererSizes() {
-    // Recalculate size for both renderers when screen size or split location changes
-
-    SCREEN_WIDTH = window.innerWidth;
-    SCREEN_HEIGHT = window.innerHeight;
-
-    screensplit_right = 1 - screensplit;
-
-    objects.normal.renderer.setSize(screensplit * SCREEN_WIDTH, SCREEN_HEIGHT);
-    objects.normal.camera.aspect = (screensplit * SCREEN_WIDTH) / SCREEN_HEIGHT;
-    objects.normal.camera.updateProjectionMatrix();
-    objects.normal.camera.setViewOffset(SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH * screensplit, SCREEN_HEIGHT);
-    objects.normal.container.style.width = screensplit * 100 + '%';
-
-    objects.logzbuf.renderer.setSize(screensplit_right * SCREEN_WIDTH, SCREEN_HEIGHT);
-    objects.logzbuf.camera.aspect = (screensplit_right * SCREEN_WIDTH) / SCREEN_HEIGHT;
-    objects.logzbuf.camera.updateProjectionMatrix();
-    objects.logzbuf.camera.setViewOffset(
-        SCREEN_WIDTH,
-        SCREEN_HEIGHT,
-        SCREEN_WIDTH * screensplit,
-        0,
-        SCREEN_WIDTH * screensplit_right,
-        SCREEN_HEIGHT,
-    );
-    objects.logzbuf.container.style.width = screensplit_right * 100 + '%';
-
-    border.style.left = screensplit * 100 + '%';
-}
-
-function animate() {
-    requestAnimationFrame(animate);
-
-    // Put some limits on zooming
-    const minzoom = labeldata[0].size * labeldata[0].scale * 1;
-    const maxzoom = labeldata[labeldata.length - 1].size * labeldata[labeldata.length - 1].scale * 100;
-    let damping = Math.abs(zoomspeed) > minzoomspeed ? 0.95 : 1.0;
-
-    // Zoom out faster the further out you go
-    const zoom = THREE.MathUtils.clamp(Math.pow(Math.E, zoompos), minzoom, maxzoom);
-    zoompos = Math.log(zoom);
-
-    // Slow down quickly at the zoom limits
-    if ((zoom == minzoom && zoomspeed < 0) || (zoom == maxzoom && zoomspeed > 0)) {
-        damping = 0.85;
-    }
-
-    zoompos += zoomspeed;
-    zoomspeed *= damping;
-
-    objects.normal.camera.position.x = Math.sin(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
-    objects.normal.camera.position.y = Math.sin(0.25 * Math.PI * (mouse[1] - 0.5)) * zoom;
-    objects.normal.camera.position.z = Math.cos(0.5 * Math.PI * (mouse[0] - 0.5)) * zoom;
-    objects.normal.camera.lookAt(objects.normal.scene.position);
-
-    // Clone camera settings across both scenes
-    objects.logzbuf.camera.position.copy(objects.normal.camera.position);
-    objects.logzbuf.camera.quaternion.copy(objects.normal.camera.quaternion);
-
-    // Update renderer sizes if the split has changed
-    if (screensplit_right != 1 - screensplit) {
-        updateRendererSizes();
-    }
-
-    objects.normal.renderer.render(objects.normal.scene, objects.normal.camera);
-    objects.logzbuf.renderer.render(objects.logzbuf.scene, objects.logzbuf.camera);
-
-    stats.update();
-}
-
-function onWindowResize() {
-    updateRendererSizes();
-}
-
-function onBorderPointerDown() {
-    // activate draggable window resizing bar
-    window.addEventListener('pointermove', onBorderPointerMove);
-    window.addEventListener('pointerup', onBorderPointerUp);
-}
-
-function onBorderPointerMove(ev) {
-    screensplit = Math.max(0, Math.min(1, ev.clientX / window.innerWidth));
-}
-
-function onBorderPointerUp() {
-    window.removeEventListener('pointermove', onBorderPointerMove);
-    window.removeEventListener('pointerup', onBorderPointerUp);
-}
-
-function onMouseMove(ev) {
-    mouse[0] = ev.clientX / window.innerWidth;
-    mouse[1] = ev.clientY / window.innerHeight;
-}
-
-function onMouseWheel(ev) {
-    const amount = ev.deltaY;
-    if (amount === 0) return;
-    const dir = amount / Math.abs(amount);
-    zoomspeed = dir / 10;
-
-    // Slow down default zoom speed after user starts zooming, to give them more control
-    minzoomspeed = 0.001;
-}
diff --git a/examples-testing/examples/webgpu_clearcoat.ts b/examples-testing/examples/webgpu_clearcoat.ts
deleted file mode 100644
index 0d5b70a2f..000000000
--- a/examples-testing/examples/webgpu_clearcoat.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js';
-
-import { FlakesTexture } from 'three/addons/textures/FlakesTexture.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-
-let particleLight;
-let group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 0.25, 50);
-    camera.position.z = 10;
-
-    scene = new THREE.Scene();
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    new HDRCubeTextureLoader()
-        .setPath('textures/cube/pisaHDR/')
-        .load(['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'], function (texture) {
-            const geometry = new THREE.SphereGeometry(0.8, 64, 32);
-
-            const textureLoader = new THREE.TextureLoader();
-
-            const diffuse = textureLoader.load('textures/carbon/Carbon.png');
-            diffuse.colorSpace = THREE.SRGBColorSpace;
-            diffuse.wrapS = THREE.RepeatWrapping;
-            diffuse.wrapT = THREE.RepeatWrapping;
-            diffuse.repeat.x = 10;
-            diffuse.repeat.y = 10;
-
-            const normalMap = textureLoader.load('textures/carbon/Carbon_Normal.png');
-            normalMap.wrapS = THREE.RepeatWrapping;
-            normalMap.wrapT = THREE.RepeatWrapping;
-            normalMap.repeat.x = 10;
-            normalMap.repeat.y = 10;
-
-            const normalMap2 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
-
-            const normalMap3 = new THREE.CanvasTexture(new FlakesTexture());
-            normalMap3.wrapS = THREE.RepeatWrapping;
-            normalMap3.wrapT = THREE.RepeatWrapping;
-            normalMap3.repeat.x = 10;
-            normalMap3.repeat.y = 6;
-            normalMap3.anisotropy = 16;
-
-            const normalMap4 = textureLoader.load('textures/golfball.jpg');
-
-            const clearcoatNormalMap = textureLoader.load(
-                'textures/pbr/Scratched_gold/Scratched_gold_01_1K_Normal.png',
-            );
-
-            // car paint
-
-            let material = new THREE.MeshPhysicalMaterial({
-                clearcoat: 1.0,
-                clearcoatRoughness: 0.1,
-                metalness: 0.9,
-                roughness: 0.5,
-                color: 0x0000ff,
-                normalMap: normalMap3,
-                normalScale: new THREE.Vector2(0.15, 0.15),
-            });
-            let mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = -1;
-            mesh.position.y = 1;
-            group.add(mesh);
-
-            // fibers
-
-            material = new THREE.MeshPhysicalMaterial({
-                roughness: 0.5,
-                clearcoat: 1.0,
-                clearcoatRoughness: 0.1,
-                map: diffuse,
-                normalMap: normalMap,
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = 1;
-            mesh.position.y = 1;
-            group.add(mesh);
-
-            // golf
-
-            material = new THREE.MeshPhysicalMaterial({
-                metalness: 0.0,
-                roughness: 0.1,
-                clearcoat: 1.0,
-                normalMap: normalMap4,
-                clearcoatNormalMap: clearcoatNormalMap,
-
-                // y scale is negated to compensate for normal map handedness.
-                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = -1;
-            mesh.position.y = -1;
-            group.add(mesh);
-
-            // clearcoat + normalmap
-
-            material = new THREE.MeshPhysicalMaterial({
-                clearcoat: 1.0,
-                metalness: 1.0,
-                color: 0xff0000,
-                normalMap: normalMap2,
-                normalScale: new THREE.Vector2(0.15, 0.15),
-                clearcoatNormalMap: clearcoatNormalMap,
-
-                // y scale is negated to compensate for normal map handedness.
-                clearcoatNormalScale: new THREE.Vector2(2.0, -2.0),
-            });
-            mesh = new THREE.Mesh(geometry, material);
-            mesh.position.x = 1;
-            mesh.position.y = -1;
-            group.add(mesh);
-
-            //
-
-            scene.background = texture;
-            scene.environment = texture;
-        });
-
-    // LIGHTS
-
-    particleLight = new THREE.Mesh(
-        new THREE.SphereGeometry(0.05, 8, 8),
-        new THREE.MeshBasicMaterial({ color: 0xffffff }),
-    );
-    scene.add(particleLight);
-
-    particleLight.add(new THREE.PointLight(0xffffff, 30));
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1.25;
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // EVENTS
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 3;
-    controls.maxDistance = 30;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-//
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-//
-
-function animate() {
-    render();
-
-    stats.update();
-}
-
-function render() {
-    const timer = Date.now() * 0.00025;
-
-    particleLight.position.x = Math.sin(timer * 7) * 3;
-    particleLight.position.y = Math.cos(timer * 5) * 4;
-    particleLight.position.z = Math.cos(timer * 3) * 3;
-
-    for (let i = 0; i < group.children.length; i++) {
-        const child = group.children[i];
-        child.rotation.y += 0.005;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_clipping.ts b/examples-testing/examples/webgpu_clipping.ts
deleted file mode 100644
index e57a7e96c..000000000
--- a/examples-testing/examples/webgpu_clipping.ts
+++ /dev/null
@@ -1,207 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, startTime, object, stats;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(36, window.innerWidth / window.innerHeight, 0.25, 16);
-
-    camera.position.set(0, 1.3, 3);
-
-    scene = new THREE.Scene();
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const spotLight = new THREE.SpotLight(0xffffff, 60);
-    spotLight.angle = Math.PI / 5;
-    spotLight.penumbra = 0.2;
-    spotLight.position.set(2, 3, 3);
-    spotLight.castShadow = true;
-    spotLight.shadow.camera.near = 3;
-    spotLight.shadow.camera.far = 10;
-    spotLight.shadow.mapSize.width = 2048;
-    spotLight.shadow.mapSize.height = 2048;
-    spotLight.shadow.bias = -0.002;
-    spotLight.shadow.radius = 4;
-    scene.add(spotLight);
-
-    const dirLight = new THREE.DirectionalLight(0x55505a, 3);
-    dirLight.position.set(0, 3, 0);
-    dirLight.castShadow = true;
-    dirLight.shadow.camera.near = 1;
-    dirLight.shadow.camera.far = 10;
-
-    dirLight.shadow.camera.right = 1;
-    dirLight.shadow.camera.left = -1;
-    dirLight.shadow.camera.top = 1;
-    dirLight.shadow.camera.bottom = -1;
-
-    dirLight.shadow.mapSize.width = 1024;
-    dirLight.shadow.mapSize.height = 1024;
-    scene.add(dirLight);
-
-    // ***** Clipping planes: *****
-
-    const localPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.8);
-    const localPlane2 = new THREE.Plane(new THREE.Vector3(0, 0, -1), 0.1);
-    const globalPlane = new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0.1);
-
-    // Geometry
-
-    const material = new THREE.MeshPhongNodeMaterial({
-        color: 0x80ee10,
-        shininess: 0,
-        side: THREE.DoubleSide,
-
-        // ***** Clipping setup (material): *****
-        clippingPlanes: [localPlane, localPlane2],
-        clipShadows: true,
-        alphaToCoverage: true,
-        clipIntersection: true,
-    });
-
-    const geometry = new THREE.TorusKnotGeometry(0.4, 0.08, 95, 20);
-
-    object = new THREE.Mesh(geometry, material);
-    object.castShadow = true;
-    scene.add(object);
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(9, 9, 1, 1),
-        new THREE.MeshPhongNodeMaterial({ color: 0xa0adaf, shininess: 150 }),
-    );
-
-    ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
-    ground.receiveShadow = true;
-    scene.add(ground);
-
-    // Stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // Renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.shadowMap.enabled = true;
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    window.addEventListener('resize', onWindowResize);
-    document.body.appendChild(renderer.domElement);
-
-    // ***** Clipping setup (renderer): *****
-    const globalPlanes = [globalPlane];
-    const Empty = Object.freeze([]);
-
-    renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
-    renderer.localClippingEnabled = true;
-
-    // Controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 1, 0);
-    controls.update();
-
-    // GUI
-
-    const gui = new GUI(),
-        props = {
-            alphaToCoverage: true,
-        },
-        folderLocal = gui.addFolder('Local Clipping'),
-        propsLocal = {
-            get Enabled() {
-                return renderer.localClippingEnabled;
-            },
-            set Enabled(v) {
-                renderer.localClippingEnabled = v;
-            },
-
-            get Shadows() {
-                return material.clipShadows;
-            },
-            set Shadows(v) {
-                material.clipShadows = v;
-            },
-
-            get Intersection() {
-                return material.clipIntersection;
-            },
-
-            set Intersection(v) {
-                material.clipIntersection = v;
-            },
-
-            get Plane() {
-                return localPlane.constant;
-            },
-            set Plane(v) {
-                localPlane.constant = v;
-            },
-        },
-        folderGlobal = gui.addFolder('Global Clipping'),
-        propsGlobal = {
-            get Enabled() {
-                return renderer.clippingPlanes !== Empty;
-            },
-            set Enabled(v) {
-                renderer.clippingPlanes = v ? globalPlanes : Empty;
-            },
-
-            get Plane() {
-                return globalPlane.constant;
-            },
-            set Plane(v) {
-                globalPlane.constant = v;
-            },
-        };
-
-    gui.add(props, 'alphaToCoverage').onChange(function (value) {
-        ground.material.alphaToCoverage = value;
-        ground.material.needsUpdate = true;
-
-        material.alphaToCoverage = value;
-        material.needsUpdate = true;
-    });
-
-    folderLocal.add(propsLocal, 'Enabled');
-    folderLocal.add(propsLocal, 'Shadows');
-    folderLocal.add(propsLocal, 'Intersection');
-    folderLocal.add(propsLocal, 'Plane', 0.3, 1.25);
-
-    folderGlobal.add(propsGlobal, 'Enabled');
-    folderGlobal.add(propsGlobal, 'Plane', -0.4, 3);
-
-    // Start
-
-    startTime = Date.now();
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate(currentTime) {
-    const time = (currentTime - startTime) / 1000;
-
-    object.position.y = 0.8;
-    object.rotation.x = time * 0.5;
-    object.rotation.y = time * 0.2;
-    object.scale.setScalar(Math.cos(time) * 0.125 + 0.875);
-
-    stats.begin();
-    renderer.render(scene, camera);
-    stats.end();
-}
diff --git a/examples-testing/examples/webgpu_custom_fog_background.ts b/examples-testing/examples/webgpu_custom_fog_background.ts
deleted file mode 100644
index 4a2e6c800..000000000
--- a/examples-testing/examples/webgpu_custom_fog_background.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import * as THREE from 'three';
-import { pass, color, rangeFog } from 'three/tsl';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer;
-let postProcessing;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    //renderer.toneMapping = THREE.ACESFilmicToneMapping; // apply tone mapping in post processing
-    container.appendChild(renderer.domElement);
-
-    // post processing
-
-    // render scene pass ( the same of css )
-    const scenePass = pass(scene, camera);
-    const scenePassViewZ = scenePass.getViewZNode();
-
-    // background color
-    const backgroundColor = color(0x0066ff);
-
-    // get fog factor from scene pass context
-    // equivalent to: scene.fog = new THREE.Fog( 0x0066ff, 2.7, 4 );
-    const fogFactor = rangeFog(null, 2.7, 4).context({ getViewZ: () => scenePassViewZ });
-
-    // tone mapping scene pass
-    const scenePassTM = scenePass.toneMapping(THREE.ACESFilmicToneMapping);
-
-    // mix fog from fog factor and background color
-    const compose = fogFactor.mix(scenePassTM, backgroundColor);
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    postProcessing.outputNode = compose;
-
-    //
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.environment = texture;
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', function (gltf) {
-            scene.add(gltf.scene);
-
-            render();
-        });
-    });
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 5;
-    controls.target.set(0, -0.1, -0.2);
-    controls.update();
-    controls.addEventListener('change', render); // use if there is no animation loop
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    postProcessing.renderAsync();
-}
diff --git a/examples-testing/examples/webgpu_display_stereo.ts b/examples-testing/examples/webgpu_display_stereo.ts
deleted file mode 100644
index 7eb45fa2a..000000000
--- a/examples-testing/examples/webgpu_display_stereo.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-
-import { stereoPass, anaglyphPass, parallaxBarrierPass } from 'three/tsl';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Timer } from 'three/addons/misc/Timer.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, postProcessing;
-
-let stereo, anaglyph, parallaxBarrier;
-
-let mesh, dummy, timer;
-
-const position = new THREE.Vector3();
-
-const params = {
-    effect: 'stereo',
-    eyeSep: 0.064,
-};
-
-const effects = { Stereo: 'stereo', Anaglyph: 'anaglyph', ParallaxBarrier: 'parallaxBarrier' };
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 3;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.CubeTextureLoader()
-        .setPath('textures/cube/Park3Med/')
-        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
-
-    timer = new Timer();
-
-    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
-
-    const textureCube = new THREE.CubeTextureLoader()
-        .setPath('textures/cube/Park3Med/')
-        .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
-
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
-
-    mesh = new THREE.InstancedMesh(geometry, material, 500);
-    mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
-
-    dummy = new THREE.Mesh();
-
-    for (let i = 0; i < 500; i++) {
-        dummy.position.x = Math.random() * 10 - 5;
-        dummy.position.y = Math.random() * 10 - 5;
-        dummy.position.z = Math.random() * 10 - 5;
-        dummy.scale.x = dummy.scale.y = dummy.scale.z = Math.random() * 3 + 1;
-
-        dummy.updateMatrix();
-
-        mesh.setMatrixAt(i, dummy.matrix);
-    }
-
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    stereo = stereoPass(scene, camera);
-    anaglyph = anaglyphPass(scene, camera);
-    parallaxBarrier = parallaxBarrierPass(scene, camera);
-
-    postProcessing.outputNode = stereo;
-
-    const gui = new GUI();
-    gui.add(params, 'effect', effects).onChange(update);
-    gui.add(params, 'eyeSep', 0.001, 0.15, 0.001).onChange(function (value) {
-        stereo.stereo.eyeSep = value;
-
-        anaglyph.stereo.eyeSep = value;
-        parallaxBarrier.stereo.eyeSep = value;
-    });
-
-    window.addEventListener('resize', onWindowResize);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 25;
-}
-
-function update(value) {
-    if (value === 'stereo') {
-        postProcessing.outputNode = stereo;
-    } else if (value === 'anaglyph') {
-        postProcessing.outputNode = anaglyph;
-    } else if (value === 'parallaxBarrier') {
-        postProcessing.outputNode = parallaxBarrier;
-    }
-
-    postProcessing.needsUpdate = true;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function extractPosition(matrix, position) {
-    position.x = matrix.elements[12];
-    position.y = matrix.elements[13];
-    position.z = matrix.elements[14];
-}
-
-function animate() {
-    timer.update();
-
-    const elapsedTime = timer.getElapsed() * 0.1;
-
-    for (let i = 0; i < mesh.count; i++) {
-        mesh.getMatrixAt(i, dummy.matrix);
-
-        extractPosition(dummy.matrix, position);
-
-        position.x = 5 * Math.cos(elapsedTime + i);
-        position.y = 5 * Math.sin(elapsedTime + i * 1.1);
-
-        dummy.matrix.setPosition(position);
-
-        mesh.setMatrixAt(i, dummy.matrix);
-
-        mesh.instanceMatrix.needsUpdate = true;
-    }
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_instancing_morph.ts b/examples-testing/examples/webgpu_instancing_morph.ts
deleted file mode 100644
index cfd721721..000000000
--- a/examples-testing/examples/webgpu_instancing_morph.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer, stats, mesh, mixer, dummy;
-
-const offset = 5000;
-
-const timeOffsets = new Float32Array(1024);
-
-for (let i = 0; i < 1024; i++) {
-    timeOffsets[i] = Math.random() * 3;
-}
-
-const clock = new THREE.Clock(true);
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 10000);
-
-    scene = new THREE.Scene();
-
-    scene.background = new THREE.Color(0x99ddff);
-
-    scene.fog = new THREE.Fog(0x99ddff, 5000, 10000);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    const light = new THREE.DirectionalLight(0xffffff, 1);
-
-    light.position.set(200, 1000, 50);
-
-    light.shadow.mapSize.width = 2048;
-    light.shadow.mapSize.height = 2048;
-    light.castShadow = true;
-
-    light.shadow.camera.left = -5000;
-    light.shadow.camera.right = 5000;
-    light.shadow.camera.top = 5000;
-    light.shadow.camera.bottom = -5000;
-    light.shadow.camera.far = 2000;
-
-    light.shadow.bias = -0.01;
-
-    light.shadow.camera.updateProjectionMatrix();
-
-    scene.add(light);
-
-    const hemi = new THREE.HemisphereLight(0x99ddff, 0x669933, 1 / 3);
-
-    scene.add(hemi);
-
-    const ground = new THREE.Mesh(
-        new THREE.PlaneGeometry(1000000, 1000000),
-        new THREE.MeshStandardMaterial({ color: 0x669933 }),
-    );
-
-    ground.rotation.x = -Math.PI / 2;
-
-    ground.receiveShadow = true;
-
-    scene.add(ground);
-
-    const loader = new GLTFLoader();
-
-    loader.load('models/gltf/Horse.glb', function (glb) {
-        dummy = glb.scene.children[0];
-
-        mesh = new THREE.InstancedMesh(
-            dummy.geometry,
-            new THREE.MeshStandardNodeMaterial({
-                flatShading: true,
-            }),
-            1024,
-        );
-
-        mesh.castShadow = true;
-
-        for (let x = 0, i = 0; x < 32; x++) {
-            for (let y = 0; y < 32; y++) {
-                dummy.position.set(offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y);
-
-                dummy.updateMatrix();
-
-                mesh.setMatrixAt(i, dummy.matrix);
-
-                mesh.setColorAt(i, new THREE.Color(`hsl(${Math.random() * 360}, 50%, 66%)`));
-
-                i++;
-            }
-        }
-
-        scene.add(mesh);
-
-        mixer = new THREE.AnimationMixer(glb.scene);
-
-        const action = mixer.clipAction(glb.animations[0]);
-
-        action.play();
-    });
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setAnimationLoop(animate);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const time = clock.getElapsedTime();
-
-    const r = 3000;
-    camera.position.set(Math.sin(time / 10) * r, 1500 + 1000 * Math.cos(time / 5), Math.cos(time / 10) * r);
-    camera.lookAt(0, 0, 0);
-
-    if (mesh) {
-        for (let i = 0; i < 1024; i++) {
-            mixer.setTime(time + timeOffsets[i]);
-
-            mesh.setMorphAt(i, dummy);
-        }
-
-        mesh.morphTexture.needsUpdate = true;
-    }
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_lightprobe.ts b/examples-testing/examples/webgpu_lightprobe.ts
deleted file mode 100644
index a34ad2dec..000000000
--- a/examples-testing/examples/webgpu_lightprobe.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
-
-let mesh, renderer, scene, camera;
-
-let gui;
-
-let lightProbe;
-let directionalLight;
-
-// linear color space
-const API = {
-    lightProbeIntensity: 1.0,
-    directionalLightIntensity: 0.6,
-    envMapIntensity: 1,
-};
-
-init();
-
-function init() {
-    // renderer
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // tone mapping
-    renderer.toneMapping = THREE.NoToneMapping;
-
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 0, 30);
-
-    // controls
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 10;
-    controls.maxDistance = 50;
-    controls.enablePan = false;
-
-    // probe
-    lightProbe = new THREE.LightProbe();
-    scene.add(lightProbe);
-
-    // light
-    directionalLight = new THREE.DirectionalLight(0xffffff, API.directionalLightIntensity);
-    directionalLight.position.set(10, 10, 10);
-    scene.add(directionalLight);
-
-    // envmap
-    const genCubeUrls = function (prefix, postfix) {
-        return [
-            prefix + 'px' + postfix,
-            prefix + 'nx' + postfix,
-            prefix + 'py' + postfix,
-            prefix + 'ny' + postfix,
-            prefix + 'pz' + postfix,
-            prefix + 'nz' + postfix,
-        ];
-    };
-
-    const urls = genCubeUrls('textures/cube/pisa/', '.png');
-
-    new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
-        scene.background = cubeTexture;
-
-        lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture));
-
-        const geometry = new THREE.SphereGeometry(5, 64, 32);
-        //const geometry = new THREE.TorusKnotGeometry( 4, 1.5, 256, 32, 2, 3 );
-
-        const material = new THREE.MeshStandardMaterial({
-            color: 0xffffff,
-            metalness: 0,
-            roughness: 0,
-            envMap: cubeTexture,
-            envMapIntensity: API.envMapIntensity,
-        });
-
-        // mesh
-        mesh = new THREE.Mesh(geometry, material);
-        scene.add(mesh);
-    });
-
-    // gui
-    gui = new GUI({ title: 'Intensity' });
-
-    gui.add(API, 'lightProbeIntensity', 0, 1, 0.02)
-        .name('light probe')
-        .onChange(function () {
-            lightProbe.intensity = API.lightProbeIntensity;
-        });
-
-    gui.add(API, 'directionalLightIntensity', 0, 1, 0.02)
-        .name('directional light')
-        .onChange(function () {
-            directionalLight.intensity = API.directionalLightIntensity;
-        });
-
-    gui.add(API, 'envMapIntensity', 0, 1, 0.02)
-        .name('envMap')
-        .onChange(function () {
-            mesh.material.envMapIntensity = API.envMapIntensity;
-        });
-
-    // listener
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_lights_ies_spotlight.ts b/examples-testing/examples/webgpu_lights_ies_spotlight.ts
deleted file mode 100644
index 41b56de88..000000000
--- a/examples-testing/examples/webgpu_lights_ies_spotlight.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from './jsm/controls/OrbitControls.js';
-
-import { IESLoader } from 'three/addons/loaders/IESLoader.js';
-
-let renderer, scene, camera;
-let lights;
-
-async function init() {
-    const iesLoader = new IESLoader().setPath('./ies/');
-    //iesLoader.type = THREE.UnsignedByteType; // LDR
-
-    const [iesTexture1, iesTexture2, iesTexture3, iesTexture4] = await Promise.all([
-        iesLoader.loadAsync('007cfb11e343e2f42e3b476be4ab684e.ies'),
-        iesLoader.loadAsync('06b4cfdc8805709e767b5e2e904be8ad.ies'),
-        iesLoader.loadAsync('02a7562c650498ebb301153dbbf59207.ies'),
-        iesLoader.loadAsync('1a936937a49c63374e6d4fbed9252b29.ies'),
-    ]);
-
-    //
-
-    scene = new THREE.Scene();
-
-    //
-
-    const spotLight = new THREE.IESSpotLight(0xff0000, 500);
-    spotLight.position.set(6.5, 1.5, 6.5);
-    spotLight.angle = Math.PI / 8;
-    spotLight.penumbra = 0.7;
-    spotLight.distance = 20;
-    spotLight.iesMap = iesTexture1;
-    scene.add(spotLight);
-
-    //
-
-    const spotLight2 = new THREE.IESSpotLight(0x00ff00, 500);
-    spotLight2.position.set(6.5, 1.5, -6.5);
-    spotLight2.angle = Math.PI / 8;
-    spotLight2.penumbra = 0.7;
-    spotLight2.distance = 20;
-    spotLight2.iesMap = iesTexture2;
-    scene.add(spotLight2);
-
-    //
-
-    const spotLight3 = new THREE.IESSpotLight(0x0000ff, 500);
-    spotLight3.position.set(-6.5, 1.5, -6.5);
-    spotLight3.angle = Math.PI / 8;
-    spotLight3.penumbra = 0.7;
-    spotLight3.distance = 20;
-    spotLight3.iesMap = iesTexture3;
-    scene.add(spotLight3);
-
-    //
-
-    const spotLight4 = new THREE.IESSpotLight(0xffffff, 500);
-    spotLight4.position.set(-6.5, 1.5, 6.5);
-    spotLight4.angle = Math.PI / 8;
-    spotLight4.penumbra = 0.7;
-    spotLight4.distance = 20;
-    spotLight4.iesMap = iesTexture4;
-    scene.add(spotLight4);
-
-    //
-
-    lights = [spotLight, spotLight2, spotLight3, spotLight4];
-
-    //
-
-    const material = new THREE.MeshPhongMaterial({ color: 0x808080 /*, dithering: true*/ });
-
-    const geometry = new THREE.PlaneGeometry(200, 200);
-
-    const mesh = new THREE.Mesh(geometry, material);
-    mesh.rotation.x = -Math.PI * 0.5;
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(16, 4, 1);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 50;
-    controls.enablePan = false;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render(time) {
-    time = (time / 1000) * 2.0;
-
-    for (let i = 0; i < lights.length; i++) {
-        lights[i].position.y = Math.sin(time + i) + 0.97;
-    }
-
-    renderer.render(scene, camera);
-}
-
-init();
diff --git a/examples-testing/examples/webgpu_lights_rectarealight.ts b/examples-testing/examples/webgpu_lights_rectarealight.ts
deleted file mode 100644
index 5638c9029..000000000
--- a/examples-testing/examples/webgpu_lights_rectarealight.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
-import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js';
-
-let renderer, scene, camera;
-let stats, meshKnot;
-
-init();
-
-function init() {
-    THREE.RectAreaLightNode.setLTC(RectAreaLightTexturesLib.init());
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animation);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.set(0, 5, -15);
-
-    scene = new THREE.Scene();
-
-    const rectLight1 = new THREE.RectAreaLight(0xff0000, 5, 4, 10);
-    rectLight1.position.set(-5, 5, 5);
-    scene.add(rectLight1);
-
-    const rectLight2 = new THREE.RectAreaLight(0x00ff00, 5, 4, 10);
-    rectLight2.position.set(0, 5, 5);
-    scene.add(rectLight2);
-
-    const rectLight3 = new THREE.RectAreaLight(0x0000ff, 5, 4, 10);
-    rectLight3.position.set(5, 5, 5);
-    scene.add(rectLight3);
-
-    scene.add(new RectAreaLightHelper(rectLight1));
-    scene.add(new RectAreaLightHelper(rectLight2));
-    scene.add(new RectAreaLightHelper(rectLight3));
-
-    const geoFloor = new THREE.BoxGeometry(2000, 0.1, 2000);
-    const matStdFloor = new THREE.MeshStandardMaterial({ color: 0xbcbcbc, roughness: 0.1, metalness: 0 });
-    const mshStdFloor = new THREE.Mesh(geoFloor, matStdFloor);
-    scene.add(mshStdFloor);
-
-    const geoKnot = new THREE.TorusKnotGeometry(1.5, 0.5, 200, 16);
-    const matKnot = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0, metalness: 0 });
-    meshKnot = new THREE.Mesh(geoKnot, matKnot);
-    meshKnot.position.set(0, 5, 0);
-    scene.add(meshKnot);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.copy(meshKnot.position);
-    controls.update();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-}
-
-function animation(time) {
-    meshKnot.rotation.y = time / 1000;
-
-    renderer.render(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf.ts b/examples-testing/examples/webgpu_loader_gltf.ts
deleted file mode 100644
index 64d1fda4b..000000000
--- a/examples-testing/examples/webgpu_loader_gltf.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as THREE from 'three';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-        //texture.minFilter = THREE.LinearMipmapLinearFilter;
-        //texture.generateMipmaps = true;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        render();
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', function (gltf) {
-            scene.add(gltf.scene);
-
-            render();
-        });
-    });
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener('change', render); // use if there is no animation loop
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-//
-
-function render() {
-    renderer.renderAsync(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts b/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
deleted file mode 100644
index d100e8c81..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_anisotropy.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let renderer, scene, camera, controls;
-
-init();
-
-async function init() {
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1.35;
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.set(-0.35, -0.2, 0.35);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, -0.08, 0.11);
-    controls.minDistance = 0.1;
-    controls.maxDistance = 2;
-    controls.addEventListener('change', render);
-    controls.update();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
-
-    const [texture, gltf] = await Promise.all([
-        rgbeLoader.loadAsync('royal_esplanade_1k.hdr'),
-        gltfLoader.loadAsync('AnisotropyBarnLamp.glb'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.backgroundBlurriness = 0.5;
-    scene.environment = texture;
-
-    // model
-
-    scene.add(gltf.scene);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render() {
-    renderer.renderAsync(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_compressed.ts b/examples-testing/examples/webgpu_loader_gltf_compressed.ts
deleted file mode 100644
index 9405b64ae..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_compressed.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as THREE from 'three';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-init();
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 20);
-    camera.position.set(2, 2, 2);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xeeeeee);
-
-    //lights
-
-    const light = new THREE.PointLight(0xffffff);
-    light.power = 1300;
-    camera.add(light);
-    scene.add(camera);
-
-    //renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    renderer.toneMappingExposure = 1;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 3;
-    controls.maxDistance = 6;
-    controls.update();
-
-    const ktx2Loader = await new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupportAsync(renderer);
-
-    const loader = new GLTFLoader();
-    loader.setKTX2Loader(ktx2Loader);
-    loader.setMeshoptDecoder(MeshoptDecoder);
-    loader.load('models/gltf/coffeemat.glb', function (gltf) {
-        const gltfScene = gltf.scene;
-        gltfScene.position.y = -0.8;
-        gltfScene.scale.setScalar(0.01);
-
-        scene.add(gltfScene);
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_dispersion.ts b/examples-testing/examples/webgpu_loader_gltf_dispersion.ts
deleted file mode 100644
index c0290b98f..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_dispersion.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer;
-
-init();
-
-async function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 5);
-    camera.position.set(0.1, 0.05, 0.15);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    renderer.toneMapping = THREE.ReinhardToneMapping; // TODO: Add THREE.NeutralToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    const rgbeLoader = await new RGBELoader()
-        .setPath('textures/equirectangular/')
-        .loadAsync('pedestrian_overpass_1k.hdr');
-    rgbeLoader.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene = new THREE.Scene();
-    scene.backgroundBlurriness = 0.5;
-    scene.environment = rgbeLoader;
-    scene.background = rgbeLoader;
-
-    const loader = new GLTFLoader();
-    const gltf = await loader.loadAsync('models/gltf/DispersionTest.glb');
-
-    scene.add(gltf.scene);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 0.1;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_iridescence.ts b/examples-testing/examples/webgpu_loader_gltf_iridescence.ts
deleted file mode 100644
index f163ea770..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_iridescence.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let renderer, scene, camera, controls;
-
-init().catch(function (err) {
-    console.error(err);
-});
-
-async function init() {
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setAnimationLoop(render);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 20);
-    camera.position.set(0.35, 0.05, 0.35);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -0.5;
-    controls.target.set(0, 0.2, 0);
-    controls.update();
-
-    const rgbeLoader = new RGBELoader().setPath('textures/equirectangular/');
-
-    const gltfLoader = new GLTFLoader().setPath('models/gltf/');
-
-    const [texture, gltf] = await Promise.all([
-        rgbeLoader.loadAsync('venice_sunset_1k.hdr'),
-        gltfLoader.loadAsync('IridescenceLamp.glb'),
-    ]);
-
-    // environment
-
-    texture.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.background = texture;
-    scene.environment = texture;
-
-    // model
-
-    scene.add(gltf.scene);
-
-    render();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    render();
-}
-
-function render() {
-    controls.update();
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_sheen.ts b/examples-testing/examples/webgpu_loader_gltf_sheen.ts
deleted file mode 100644
index 788ef2a89..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_sheen.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, controls;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
-    camera.position.set(-0.75, 0.7, 1.25);
-
-    scene = new THREE.Scene();
-    //scene.add( new THREE.DirectionalLight( 0xffffff, 2 ) );
-
-    // model
-
-    new GLTFLoader().setPath('models/gltf/').load('SheenChair.glb', function (gltf) {
-        scene.add(gltf.scene);
-
-        const object = gltf.scene.getObjectByName('SheenChair_fabric');
-
-        const gui = new GUI();
-
-        gui.add(object.material, 'sheen', 0, 1);
-        gui.open();
-    });
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    scene.background = new THREE.Color(0xaaaaaa);
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        //scene.backgroundBlurriness = 1; // @TODO: Needs PMREM
-        scene.environment = texture;
-    });
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0.35, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    controls.update(); // required if damping enabled
-
-    render();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_loader_gltf_transmission.ts b/examples-testing/examples/webgpu_loader_gltf_transmission.ts
deleted file mode 100644
index 040233262..000000000
--- a/examples-testing/examples/webgpu_loader_gltf_transmission.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-let camera, scene, renderer, controls, clock, mixer;
-
-init();
-
-function init() {
-    clock = new THREE.Clock();
-
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(0, 0.4, 0.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.backgroundBlurriness = 0.35;
-
-        scene.environment = texture;
-
-        // model
-
-        new GLTFLoader()
-            .setPath('models/gltf/')
-            .setDRACOLoader(new DRACOLoader().setDecoderPath('jsm/libs/draco/gltf/'))
-            .load('IridescentDishWithOlives.glb', function (gltf) {
-                mixer = new THREE.AnimationMixer(gltf.scene);
-                mixer.clipAction(gltf.animations[0]).play();
-
-                scene.add(gltf.scene);
-            });
-    });
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setAnimationLoop(render);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    container.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -0.75;
-    controls.enableDamping = true;
-    controls.minDistance = 0.5;
-    controls.maxDistance = 1;
-    controls.target.set(0, 0.1, 0);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    if (mixer) mixer.update(clock.getDelta());
-
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_materials_basic.ts b/examples-testing/examples/webgpu_materials_basic.ts
deleted file mode 100644
index 0161a9c7b..000000000
--- a/examples-testing/examples/webgpu_materials_basic.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-const spheres = [];
-
-let mouseX = 0;
-let mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-const params = {
-    color: '#ffffff',
-    mapping: THREE.CubeReflectionMapping,
-    refractionRatio: 0.98,
-    transparent: false,
-    opacity: 1,
-};
-
-const mappings = { ReflectionMapping: THREE.CubeReflectionMapping, RefractionMapping: THREE.CubeRefractionMapping };
-
-document.addEventListener('mousemove', onDocumentMouseMove);
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 100);
-    camera.position.z = 3;
-
-    const path = './textures/cube/pisa/';
-    const format = '.png';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const textureCube = new THREE.CubeTextureLoader().load(urls);
-
-    scene = new THREE.Scene();
-    scene.background = textureCube;
-
-    const geometry = new THREE.SphereGeometry(0.1, 32, 16);
-    const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: textureCube });
-
-    for (let i = 0; i < 500; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = Math.random() * 10 - 5;
-        mesh.position.y = Math.random() * 10 - 5;
-        mesh.position.z = Math.random() * 10 - 5;
-
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1;
-
-        scene.add(mesh);
-
-        spheres.push(mesh);
-    }
-
-    //
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const gui = new GUI({ width: 300 });
-
-    gui.addColor(params, 'color').onChange(value => material.color.set(value));
-    gui.add(params, 'mapping', mappings).onChange(value => {
-        textureCube.mapping = value;
-        material.needsUpdate = true;
-    });
-    gui.add(params, 'refractionRatio')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(value => (material.refractionRatio = value));
-    gui.add(params, 'transparent').onChange(value => {
-        material.transparent = value;
-        material.needsUpdate = true;
-    });
-    gui.add(params, 'opacity')
-        .min(0.0)
-        .max(1.0)
-        .step(0.01)
-        .onChange(value => (material.opacity = value));
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = (event.clientX - windowHalfX) / 100;
-    mouseY = (event.clientY - windowHalfY) / 100;
-}
-
-//
-
-function animate() {
-    const timer = 0.0001 * Date.now();
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0, il = spheres.length; i < il; i++) {
-        const sphere = spheres[i];
-
-        sphere.position.x = 5 * Math.cos(timer + i);
-        sphere.position.y = 5 * Math.sin(timer + i * 1.1);
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_materials_displacementmap.ts b/examples-testing/examples/webgpu_materials_displacementmap.ts
deleted file mode 100644
index 54d26d65e..000000000
--- a/examples-testing/examples/webgpu_materials_displacementmap.ts
+++ /dev/null
@@ -1,224 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-
-let stats;
-let camera, scene, renderer, controls;
-
-const settings = {
-    metalness: 1.0,
-    roughness: 0.4,
-    ambientIntensity: 0.2,
-    aoMapIntensity: 1.0,
-    envMapIntensity: 1.0,
-    displacementScale: 2.436143, // from original model
-    normalScale: 1.0,
-};
-
-let mesh, material;
-
-let pointLight, ambientLight;
-
-const height = 500; // of camera frustum
-
-let r = 0.0;
-
-init();
-initGui();
-
-// Init gui
-function initGui() {
-    const gui = new GUI();
-
-    gui.add(settings, 'metalness')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.metalness = value;
-        });
-
-    gui.add(settings, 'roughness')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.roughness = value;
-        });
-
-    gui.add(settings, 'aoMapIntensity')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            material.aoMapIntensity = value;
-        });
-
-    gui.add(settings, 'ambientIntensity')
-        .min(0)
-        .max(1)
-        .onChange(function (value) {
-            ambientLight.intensity = value;
-        });
-
-    gui.add(settings, 'envMapIntensity')
-        .min(0)
-        .max(3)
-        .onChange(function (value) {
-            material.envMapIntensity = value;
-        });
-
-    gui.add(settings, 'displacementScale')
-        .min(0)
-        .max(3.0)
-        .onChange(function (value) {
-            material.displacementScale = value;
-        });
-
-    gui.add(settings, 'normalScale')
-        .min(-1)
-        .max(1)
-        .onChange(function (value) {
-            material.normalScale.set(1, -1).multiplyScalar(value);
-        });
-}
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setAnimationLoop(animate);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    scene = new THREE.Scene();
-
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera(-height * aspect, height * aspect, height, -height, 1, 10000);
-    camera.position.z = 1500;
-    scene.add(camera);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-    controls.enableDamping = true;
-
-    // lights
-
-    ambientLight = new THREE.AmbientLight(0xffffff, settings.ambientIntensity);
-    scene.add(ambientLight);
-
-    pointLight = new THREE.PointLight(0xff0000, 1.5, 0, 0);
-    pointLight.position.z = 2500;
-    scene.add(pointLight);
-
-    const pointLight2 = new THREE.PointLight(0xff6666, 3, 0, 0);
-    camera.add(pointLight2);
-
-    const pointLight3 = new THREE.PointLight(0x0000ff, 1.5, 0, 0);
-    pointLight3.position.x = -1000;
-    pointLight3.position.z = 1000;
-    scene.add(pointLight3);
-
-    // env map
-
-    const path = 'textures/cube/SwedishRoyalCastle/';
-    const format = '.jpg';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const reflectionCube = new THREE.CubeTextureLoader().load(urls);
-
-    // textures
-
-    const textureLoader = new THREE.TextureLoader();
-    const normalMap = textureLoader.load('models/obj/ninja/normal.png');
-    const aoMap = textureLoader.load('models/obj/ninja/ao.jpg');
-    const displacementMap = textureLoader.load('models/obj/ninja/displacement.jpg');
-
-    // material
-
-    material = new THREE.MeshStandardNodeMaterial({
-        color: 0xc1c1c1,
-        roughness: settings.roughness,
-        metalness: settings.metalness,
-
-        normalMap: normalMap,
-        normalScale: new THREE.Vector2(1, -1), // why does the normal map require negation in this case?
-
-        aoMap: aoMap,
-        aoMapIntensity: 1,
-
-        displacementMap: displacementMap,
-        displacementScale: settings.displacementScale,
-        displacementBias: -0.428408, // from original model
-
-        envMap: reflectionCube,
-        envMapIntensity: settings.envMapIntensity,
-
-        side: THREE.DoubleSide,
-    });
-
-    //
-
-    const loader = new OBJLoader();
-    loader.load('models/obj/ninja/ninjaHead_Low.obj', function (group) {
-        const geometry = group.children[0].geometry;
-        geometry.center();
-
-        mesh = new THREE.Mesh(geometry, material);
-        mesh.scale.multiplyScalar(25);
-        scene.add(mesh);
-    });
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera.left = -height * aspect;
-    camera.right = height * aspect;
-    camera.top = height;
-    camera.bottom = -height;
-
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    controls.update();
-
-    stats.begin();
-    render();
-    stats.end();
-}
-
-function render() {
-    pointLight.position.x = 2500 * Math.cos(r);
-    pointLight.position.z = 2500 * Math.sin(r);
-
-    r += 0.01;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_materials_envmaps.ts b/examples-testing/examples/webgpu_materials_envmaps.ts
deleted file mode 100644
index f49b4ca1e..000000000
--- a/examples-testing/examples/webgpu_materials_envmaps.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let controls, camera, scene, renderer;
-let textureEquirec, textureCube;
-let sphereMesh, sphereMaterial, params;
-
-init();
-
-function init() {
-    // CAMERAS
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 0, 2.5);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    // Textures
-
-    const loader = new THREE.CubeTextureLoader();
-    loader.setPath('textures/cube/Bridge2/');
-
-    textureCube = loader.load(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
-
-    const textureLoader = new THREE.TextureLoader();
-
-    textureEquirec = textureLoader.load('textures/2294472375_24a3b8ef46_o.jpg');
-    textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
-    textureEquirec.colorSpace = THREE.SRGBColorSpace;
-
-    scene.background = textureCube;
-
-    //
-
-    const geometry = new THREE.IcosahedronGeometry(1, 15);
-    sphereMaterial = new THREE.MeshBasicMaterial({ envMap: textureCube });
-    sphereMesh = new THREE.Mesh(geometry, sphereMaterial);
-    scene.add(sphereMesh);
-
-    //
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1.5;
-    controls.maxDistance = 6;
-
-    //
-
-    params = {
-        Cube: function () {
-            scene.background = textureCube;
-
-            sphereMaterial.envMap = textureCube;
-            sphereMaterial.needsUpdate = true;
-        },
-        Equirectangular: function () {
-            scene.background = textureEquirec;
-
-            sphereMaterial.envMap = textureEquirec;
-            sphereMaterial.needsUpdate = true;
-        },
-        Refraction: false,
-    };
-
-    const gui = new GUI({ width: 300 });
-    gui.add(params, 'Cube');
-    gui.add(params, 'Equirectangular');
-    gui.add(params, 'Refraction').onChange(function (value) {
-        if (value) {
-            textureEquirec.mapping = THREE.EquirectangularRefractionMapping;
-            textureCube.mapping = THREE.CubeRefractionMapping;
-        } else {
-            textureEquirec.mapping = THREE.EquirectangularReflectionMapping;
-            textureCube.mapping = THREE.CubeReflectionMapping;
-        }
-
-        sphereMaterial.needsUpdate = true;
-    });
-    gui.open();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    camera.lookAt(scene.position);
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_materials_lightmap.ts b/examples-testing/examples/webgpu_materials_lightmap.ts
deleted file mode 100644
index 616645aab..000000000
--- a/examples-testing/examples/webgpu_materials_lightmap.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import * as THREE from 'three';
-import { vec4, color, positionLocal, mix } from 'three/tsl';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, stats;
-let camera, scene, renderer;
-
-init();
-
-async function init() {
-    const { innerWidth, innerHeight } = window;
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // CAMERA
-
-    camera = new THREE.PerspectiveCamera(40, innerWidth / innerHeight, 1, 10000);
-    camera.position.set(700, 200, -500);
-
-    // SCENE
-
-    scene = new THREE.Scene();
-
-    // LIGHTS
-
-    const light = new THREE.DirectionalLight(0xd5deff);
-    light.position.x = 300;
-    light.position.y = 250;
-    light.position.z = -500;
-    scene.add(light);
-
-    // SKYDOME
-
-    const topColor = new THREE.Color().copy(light.color);
-    const bottomColor = new THREE.Color(0xffffff);
-    const offset = 400;
-    const exponent = 0.6;
-
-    const h = positionLocal.add(offset).normalize().y;
-
-    const skyMat = new THREE.MeshBasicNodeMaterial();
-    skyMat.colorNode = vec4(mix(color(bottomColor), color(topColor), h.max(0.0).pow(exponent)), 1.0);
-    skyMat.side = THREE.BackSide;
-
-    const sky = new THREE.Mesh(new THREE.SphereGeometry(4000, 32, 15), skyMat);
-    scene.add(sky);
-
-    // MODEL
-
-    const loader = new THREE.ObjectLoader();
-    const object = await loader.loadAsync('models/json/lightmap/lightmap.json');
-    scene.add(object);
-
-    // RENDERER
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setAnimationLoop(animate);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(innerWidth, innerHeight);
-    container.appendChild(renderer.domElement);
-
-    // CONTROLS
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = (0.9 * Math.PI) / 2;
-    controls.enableZoom = false;
-
-    // STATS
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_materials_toon.ts b/examples-testing/examples/webgpu_materials_toon.ts
deleted file mode 100644
index 217460596..000000000
--- a/examples-testing/examples/webgpu_materials_toon.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { FontLoader } from 'three/addons/loaders/FontLoader.js';
-import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
-
-let container, stats;
-
-let camera, scene, renderer;
-let particleLight;
-
-const loader = new FontLoader();
-loader.load('fonts/gentilis_regular.typeface.json', function (font) {
-    init(font);
-});
-
-function init(font) {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2500);
-    camera.position.set(0.0, 400, 400 * 3.5);
-
-    //
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x444488);
-
-    //
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    container.appendChild(renderer.domElement);
-
-    // Materials
-
-    const cubeWidth = 400;
-    const numberOfSphersPerSide = 5;
-    const sphereRadius = (cubeWidth / numberOfSphersPerSide) * 0.8 * 0.5;
-    const stepSize = 1.0 / numberOfSphersPerSide;
-
-    const geometry = new THREE.SphereGeometry(sphereRadius, 32, 16);
-
-    for (let alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex++) {
-        const colors = new Uint8Array(alphaIndex + 2);
-
-        for (let c = 0; c <= colors.length; c++) {
-            colors[c] = (c / colors.length) * 256;
-        }
-
-        const gradientMap = new THREE.DataTexture(colors, colors.length, 1, THREE.RedFormat);
-        gradientMap.needsUpdate = true;
-
-        for (let beta = 0; beta <= 1.0; beta += stepSize) {
-            for (let gamma = 0; gamma <= 1.0; gamma += stepSize) {
-                // basic monochromatic energy preservation
-                const diffuseColor = new THREE.Color()
-                    .setHSL(alpha, 0.5, gamma * 0.5 + 0.1)
-                    .multiplyScalar(1 - beta * 0.2);
-
-                const material = new THREE.MeshToonNodeMaterial({
-                    color: diffuseColor,
-                    gradientMap: gradientMap,
-                });
-
-                const mesh = new THREE.Mesh(geometry, material);
-
-                mesh.position.x = alpha * 400 - 200;
-                mesh.position.y = beta * 400 - 200;
-                mesh.position.z = gamma * 400 - 200;
-
-                scene.add(mesh);
-            }
-        }
-    }
-
-    function addLabel(name, location) {
-        const textGeo = new TextGeometry(name, {
-            font: font,
-
-            size: 20,
-            depth: 1,
-            curveSegments: 1,
-        });
-
-        const textMaterial = new THREE.MeshBasicNodeMaterial();
-        const textMesh = new THREE.Mesh(textGeo, textMaterial);
-        textMesh.position.copy(location);
-        scene.add(textMesh);
-    }
-
-    addLabel('-gradientMap', new THREE.Vector3(-350, 0, 0));
-    addLabel('+gradientMap', new THREE.Vector3(350, 0, 0));
-
-    addLabel('-diffuse', new THREE.Vector3(0, 0, -300));
-    addLabel('+diffuse', new THREE.Vector3(0, 0, 300));
-
-    particleLight = new THREE.Mesh(
-        new THREE.SphereGeometry(4, 8, 8),
-        new THREE.MeshBasicNodeMaterial({ color: 0xffffff }),
-    );
-    scene.add(particleLight);
-
-    // Lights
-
-    scene.add(new THREE.AmbientLight(0xc1c1c1, 3));
-
-    const pointLight = new THREE.PointLight(0xffffff, 2, 800, 0);
-    particleLight.add(pointLight);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 200;
-    controls.maxDistance = 2000;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    const timer = Date.now() * 0.00025;
-
-    particleLight.position.x = Math.sin(timer * 7) * 300;
-    particleLight.position.y = Math.cos(timer * 5) * 400;
-    particleLight.position.z = Math.cos(timer * 3) * 300;
-
-    stats.begin();
-
-    renderer.render(scene, camera);
-
-    stats.end();
-}
diff --git a/examples-testing/examples/webgpu_materials_transmission.ts b/examples-testing/examples/webgpu_materials_transmission.ts
deleted file mode 100644
index 0e04ddad9..000000000
--- a/examples-testing/examples/webgpu_materials_transmission.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-const params = {
-    color: 0xffffff,
-    transmission: 1,
-    opacity: 1,
-    metalness: 0,
-    roughness: 0,
-    ior: 1.5,
-    thickness: 0.01,
-    specularIntensity: 1,
-    specularColor: 0xffffff,
-    envMapIntensity: 1,
-    lightIntensity: 1,
-    exposure: 1,
-};
-
-let camera, scene, renderer;
-
-let mesh;
-
-const hdrEquirect = new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function () {
-    hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
-
-    init();
-    render();
-});
-
-function init() {
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    document.body.appendChild(renderer.domElement);
-
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = params.exposure;
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.position.set(0, 0, 120);
-
-    //
-
-    scene.background = hdrEquirect;
-
-    //
-
-    const geometry = new THREE.SphereGeometry(20, 64, 32);
-
-    const texture = new THREE.CanvasTexture(generateTexture());
-    texture.magFilter = THREE.NearestFilter;
-    texture.wrapT = THREE.RepeatWrapping;
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.repeat.set(1, 3.5);
-
-    const material = new THREE.MeshPhysicalMaterial({
-        color: params.color,
-        metalness: params.metalness,
-        roughness: params.roughness,
-        ior: params.ior,
-        alphaMap: texture,
-        envMap: hdrEquirect,
-        envMapIntensity: params.envMapIntensity,
-        transmission: params.transmission, // use material.transmission for glass materials
-        specularIntensity: params.specularIntensity,
-        specularColor: params.specularColor,
-        opacity: params.opacity,
-        side: THREE.DoubleSide,
-        transparent: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 10;
-    controls.maxDistance = 150;
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    gui.addColor(params, 'color').onChange(function () {
-        material.color.set(params.color);
-    });
-
-    gui.add(params, 'transmission', 0, 1, 0.01).onChange(function () {
-        material.transmission = params.transmission;
-    });
-
-    gui.add(params, 'opacity', 0, 1, 0.01).onChange(function () {
-        material.opacity = params.opacity;
-    });
-
-    gui.add(params, 'metalness', 0, 1, 0.01).onChange(function () {
-        material.metalness = params.metalness;
-    });
-
-    gui.add(params, 'roughness', 0, 1, 0.01).onChange(function () {
-        material.roughness = params.roughness;
-    });
-
-    gui.add(params, 'ior', 1, 2, 0.01).onChange(function () {
-        material.ior = params.ior;
-    });
-
-    gui.add(params, 'thickness', 0, 5, 0.01).onChange(function () {
-        material.thickness = params.thickness;
-    });
-
-    gui.add(params, 'specularIntensity', 0, 1, 0.01).onChange(function () {
-        material.specularIntensity = params.specularIntensity;
-    });
-
-    gui.addColor(params, 'specularColor').onChange(function () {
-        material.specularColor.set(params.specularColor);
-    });
-
-    gui.add(params, 'envMapIntensity', 0, 1, 0.01)
-        .name('envMap intensity')
-        .onChange(function () {
-            material.envMapIntensity = params.envMapIntensity;
-        });
-
-    gui.add(params, 'exposure', 0, 1, 0.01).onChange(function () {
-        renderer.toneMappingExposure = params.exposure;
-    });
-
-    gui.open();
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-//
-
-function generateTexture() {
-    const canvas = document.createElement('canvas');
-    canvas.width = 2;
-    canvas.height = 2;
-
-    const context = canvas.getContext('2d');
-    context.fillStyle = 'white';
-    context.fillRect(0, 1, 2, 1);
-
-    return canvas;
-}
-
-function render() {
-    renderer.renderAsync(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_materials_video.ts b/examples-testing/examples/webgpu_materials_video.ts
deleted file mode 100644
index bd84aba0f..000000000
--- a/examples-testing/examples/webgpu_materials_video.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import * as THREE from 'three';
-
-let container;
-
-let camera, scene, renderer;
-
-let video, texture, material, mesh;
-
-let mouseX = 0;
-let mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-let cube_count;
-
-const meshes = [],
-    materials = [],
-    xgrid = 20,
-    ygrid = 10;
-
-const startButton = document.getElementById('startButton');
-startButton.addEventListener('click', function () {
-    init();
-});
-
-function init() {
-    const overlay = document.getElementById('overlay');
-    overlay.remove();
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
-    camera.position.z = 500;
-
-    scene = new THREE.Scene();
-
-    const light = new THREE.DirectionalLight(0xffffff, 7);
-    light.position.set(0.5, 1, 1).normalize();
-    scene.add(light);
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    container.appendChild(renderer.domElement);
-
-    video = document.getElementById('video');
-    video.play();
-    video.addEventListener('play', function () {
-        this.currentTime = 3;
-    });
-
-    texture = new THREE.VideoTexture(video);
-
-    //
-
-    let i, j, ox, oy, geometry;
-
-    const ux = 1 / xgrid;
-    const uy = 1 / ygrid;
-
-    const xsize = 480 / xgrid;
-    const ysize = 204 / ygrid;
-
-    const parameters = { color: 0xffffff, map: texture };
-
-    cube_count = 0;
-
-    for (i = 0; i < xgrid; i++) {
-        for (j = 0; j < ygrid; j++) {
-            ox = i;
-            oy = j;
-
-            geometry = new THREE.BoxGeometry(xsize, ysize, xsize);
-
-            change_uvs(geometry, ux, uy, ox, oy);
-
-            materials[cube_count] = new THREE.MeshPhongMaterial(parameters);
-
-            material = materials[cube_count];
-
-            material.hue = i / xgrid;
-            material.saturation = 1 - j / ygrid;
-
-            material.color.setHSL(material.hue, material.saturation, 0.5);
-
-            mesh = new THREE.Mesh(geometry, material);
-
-            mesh.position.x = (i - xgrid / 2) * xsize;
-            mesh.position.y = (j - ygrid / 2) * ysize;
-            mesh.position.z = 0;
-
-            mesh.scale.x = mesh.scale.y = mesh.scale.z = 1;
-
-            scene.add(mesh);
-
-            mesh.dx = 0.001 * (0.5 - Math.random());
-            mesh.dy = 0.001 * (0.5 - Math.random());
-
-            meshes[cube_count] = mesh;
-
-            cube_count += 1;
-        }
-    }
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function change_uvs(geometry, unitx, unity, offsetx, offsety) {
-    const uvs = geometry.attributes.uv.array;
-
-    for (let i = 0; i < uvs.length; i += 2) {
-        uvs[i] = (uvs[i] + offsetx) * unitx;
-        uvs[i + 1] = (uvs[i + 1] + offsety) * unity;
-    }
-}
-
-function onDocumentMouseMove(event) {
-    mouseX = event.clientX - windowHalfX;
-    mouseY = (event.clientY - windowHalfY) * 0.3;
-}
-
-//
-
-let h,
-    counter = 1;
-
-function render() {
-    const time = Date.now() * 0.00005;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y += (-mouseY - camera.position.y) * 0.05;
-
-    camera.lookAt(scene.position);
-
-    for (let i = 0; i < cube_count; i++) {
-        material = materials[i];
-
-        h = ((360 * (material.hue + time)) % 360) / 360;
-        material.color.setHSL(h, material.saturation, 0.5);
-    }
-
-    if (counter % 1000 > 200) {
-        for (let i = 0; i < cube_count; i++) {
-            mesh = meshes[i];
-
-            mesh.rotation.x += 10 * mesh.dx;
-            mesh.rotation.y += 10 * mesh.dy;
-
-            mesh.position.x -= 150 * mesh.dx;
-            mesh.position.y += 150 * mesh.dy;
-            mesh.position.z += 300 * mesh.dx;
-        }
-    }
-
-    if (counter % 1000 === 0) {
-        for (let i = 0; i < cube_count; i++) {
-            mesh = meshes[i];
-
-            mesh.dx *= -1;
-            mesh.dy *= -1;
-        }
-    }
-
-    counter++;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_mesh_batch.ts b/examples-testing/examples/webgpu_mesh_batch.ts
deleted file mode 100644
index 48c6fad63..000000000
--- a/examples-testing/examples/webgpu_mesh_batch.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { radixSort } from 'three/addons/utils/SortUtils.js';
-
-import { transformedNormalView, directionToColor, diffuseColor } from 'three/tsl';
-
-let camera, scene, renderer;
-let controls, stats;
-let gui;
-let geometries, mesh, material;
-const ids = [];
-
-const matrix = new THREE.Matrix4();
-
-//
-
-const position = new THREE.Vector3();
-const rotation = new THREE.Euler();
-const quaternion = new THREE.Quaternion();
-const scale = new THREE.Vector3();
-
-//
-
-const MAX_GEOMETRY_COUNT = 20000;
-
-const api = {
-    webgpu: true,
-    count: 512,
-    dynamic: 16,
-
-    sortObjects: true,
-    perObjectFrustumCulled: true,
-    opacity: 1,
-    useCustomSort: true,
-};
-
-init();
-
-//
-
-function randomizeMatrix(matrix) {
-    position.x = Math.random() * 40 - 20;
-    position.y = Math.random() * 40 - 20;
-    position.z = Math.random() * 40 - 20;
-
-    rotation.x = Math.random() * 2 * Math.PI;
-    rotation.y = Math.random() * 2 * Math.PI;
-    rotation.z = Math.random() * 2 * Math.PI;
-
-    quaternion.setFromEuler(rotation);
-
-    scale.x = scale.y = scale.z = 0.5 + Math.random() * 0.5;
-
-    return matrix.compose(position, quaternion, scale);
-}
-
-function randomizeRotationSpeed(rotation) {
-    rotation.x = Math.random() * 0.01;
-    rotation.y = Math.random() * 0.01;
-    rotation.z = Math.random() * 0.01;
-    return rotation;
-}
-
-function initGeometries() {
-    geometries = [
-        new THREE.ConeGeometry(1.0, 2.0),
-        new THREE.BoxGeometry(2.0, 2.0, 2.0),
-        new THREE.SphereGeometry(1.0, 16, 8),
-    ];
-}
-
-function createMaterial() {
-    if (!material) {
-        material = new THREE.MeshBasicNodeMaterial();
-        material.outputNode = diffuseColor.mul(directionToColor(transformedNormalView).y.add(0.5));
-    }
-
-    return material;
-}
-
-function cleanup() {
-    if (mesh) {
-        mesh.parent.remove(mesh);
-
-        if (mesh.dispose) {
-            mesh.dispose();
-        }
-    }
-}
-
-function initMesh() {
-    cleanup();
-    initBatchedMesh();
-}
-
-function initBatchedMesh() {
-    const geometryCount = api.count;
-    const vertexCount = geometries.length * 512;
-    const indexCount = geometries.length * 1024;
-
-    const euler = new THREE.Euler();
-    const matrix = new THREE.Matrix4();
-    mesh = new THREE.BatchedMesh(geometryCount, vertexCount, indexCount, createMaterial());
-    mesh.userData.rotationSpeeds = [];
-
-    // disable full-object frustum culling since all of the objects can be dynamic.
-    mesh.frustumCulled = false;
-
-    ids.length = 0;
-
-    const geometryIds = [
-        mesh.addGeometry(geometries[0]),
-        mesh.addGeometry(geometries[1]),
-        mesh.addGeometry(geometries[2]),
-    ];
-    for (let i = 0; i < api.count; i++) {
-        const id = mesh.addInstance(geometryIds[i % geometryIds.length]);
-        mesh.setMatrixAt(id, randomizeMatrix(matrix));
-        mesh.setColorAt(id, new THREE.Color(Math.random() * 0xffffff));
-
-        const rotationMatrix = new THREE.Matrix4();
-        rotationMatrix.makeRotationFromEuler(randomizeRotationSpeed(euler));
-        mesh.userData.rotationSpeeds.push(rotationMatrix);
-
-        ids.push(id);
-    }
-
-    scene.add(mesh);
-}
-
-function init(forceWebGL = false) {
-    if (renderer) {
-        renderer.dispose();
-        controls.dispose();
-        document.body.removeChild(stats.dom);
-        document.body.removeChild(renderer.domElement);
-    }
-
-    // camera
-
-    const aspect = window.innerWidth / window.innerHeight;
-
-    camera = new THREE.PerspectiveCamera(70, aspect, 1, 100);
-    camera.position.z = 30;
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    renderer.setAnimationLoop(animate);
-
-    // scene
-
-    scene = new THREE.Scene();
-    scene.background = forceWebGL ? new THREE.Color(0xffc1c1) : new THREE.Color(0xc1c1ff);
-
-    document.body.appendChild(renderer.domElement);
-
-    initGeometries();
-    initMesh();
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = 1.0;
-
-    // stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // gui
-
-    gui = new GUI();
-    gui.add(api, 'webgpu').onChange(() => {
-        init(!api.webgpu);
-    });
-    gui.add(api, 'count', 1, MAX_GEOMETRY_COUNT).step(1).onChange(initMesh);
-    gui.add(api, 'dynamic', 0, MAX_GEOMETRY_COUNT).step(1);
-
-    gui.add(api, 'opacity', 0, 1).onChange(v => {
-        if (v < 1) {
-            material.transparent = true;
-            material.depthWrite = false;
-        } else {
-            material.transparent = false;
-            material.depthWrite = true;
-        }
-
-        material.opacity = v;
-        material.needsUpdate = true;
-    });
-    gui.add(api, 'sortObjects');
-    gui.add(api, 'perObjectFrustumCulled');
-    gui.add(api, 'useCustomSort');
-
-    // listeners
-
-    window.addEventListener('resize', onWindowResize);
-
-    function onWindowResize() {
-        const width = window.innerWidth;
-        const height = window.innerHeight;
-
-        camera.aspect = width / height;
-        camera.updateProjectionMatrix();
-
-        renderer.setSize(width, height);
-    }
-
-    async function animate() {
-        animateMeshes();
-
-        controls.update();
-
-        if (mesh.isBatchedMesh) {
-            mesh.sortObjects = api.sortObjects;
-            mesh.perObjectFrustumCulled = api.perObjectFrustumCulled;
-            mesh.setCustomSort(api.useCustomSort ? sortFunction : null);
-        }
-
-        await renderer.renderAsync(scene, camera);
-
-        stats.update();
-    }
-
-    function animateMeshes() {
-        const loopNum = Math.min(api.count, api.dynamic);
-
-        for (let i = 0; i < loopNum; i++) {
-            const rotationMatrix = mesh.userData.rotationSpeeds[i];
-            const id = ids[i];
-
-            mesh.getMatrixAt(id, matrix);
-            matrix.multiply(rotationMatrix);
-            mesh.setMatrixAt(id, matrix);
-        }
-    }
-}
-
-//
-
-function sortFunction(list, camera) {
-    // initialize options
-    this._options = this._options || {
-        get: el => el.z,
-        aux: new Array(this.maxInstanceCount),
-    };
-
-    const options = this._options;
-    options.reversed = this.material.transparent;
-
-    // convert depth to unsigned 32 bit range
-    const factor = (2 ** 32 - 1) / camera.far; // UINT32_MAX / max_depth
-    for (let i = 0, l = list.length; i < l; i++) {
-        list[i].z *= factor;
-    }
-
-    // perform a fast-sort using the hybrid radix sort function
-    radixSort(list, options);
-}
diff --git a/examples-testing/examples/webgpu_morphtargets.ts b/examples-testing/examples/webgpu_morphtargets.ts
deleted file mode 100644
index 9fb7075cb..000000000
--- a/examples-testing/examples/webgpu_morphtargets.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let container, camera, scene, renderer, mesh;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x8fbcd4);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
-    camera.position.z = 10;
-    scene.add(camera);
-
-    scene.add(new THREE.AmbientLight(0x8fbcd4, 1.5));
-
-    const pointLight = new THREE.PointLight(0xffffff, 200);
-    camera.add(pointLight);
-
-    const geometry = createGeometry();
-
-    const material = new THREE.MeshPhongMaterial({
-        color: 0xff0000,
-        flatShading: true,
-    });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    initGUI();
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(function () {
-        renderer.render(scene, camera);
-    });
-    container.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createGeometry() {
-    const geometry = new THREE.BoxGeometry(2, 2, 2, 32, 32, 32);
-
-    // create an empty array to hold targets for the attribute we want to morph
-    // morphing positions and normals is supported
-    geometry.morphAttributes.position = [];
-
-    // the original positions of the cube's vertices
-    const positionAttribute = geometry.attributes.position;
-
-    // for the first morph target we'll move the cube's vertices onto the surface of a sphere
-    const spherePositions = [];
-
-    // for the second morph target, we'll twist the cubes vertices
-    const twistPositions = [];
-    const direction = new THREE.Vector3(1, 0, 0);
-    const vertex = new THREE.Vector3();
-
-    for (let i = 0; i < positionAttribute.count; i++) {
-        const x = positionAttribute.getX(i);
-        const y = positionAttribute.getY(i);
-        const z = positionAttribute.getZ(i);
-
-        spherePositions.push(
-            x * Math.sqrt(1 - (y * y) / 2 - (z * z) / 2 + (y * y * z * z) / 3),
-            y * Math.sqrt(1 - (z * z) / 2 - (x * x) / 2 + (z * z * x * x) / 3),
-            z * Math.sqrt(1 - (x * x) / 2 - (y * y) / 2 + (x * x * y * y) / 3),
-        );
-
-        // stretch along the x-axis so we can see the twist better
-        vertex.set(x * 2, y, z);
-
-        vertex.applyAxisAngle(direction, (Math.PI * x) / 2).toArray(twistPositions, twistPositions.length);
-    }
-
-    // add the spherical positions as the first morph target
-    geometry.morphAttributes.position[0] = new THREE.Float32BufferAttribute(spherePositions, 3);
-
-    // add the twisted positions as the second morph target
-    geometry.morphAttributes.position[1] = new THREE.Float32BufferAttribute(twistPositions, 3);
-
-    return geometry;
-}
-
-function initGUI() {
-    // Set up dat.GUI to control targets
-    const params = {
-        Spherify: 0,
-        Twist: 0,
-    };
-    const gui = new GUI({ title: 'Morph Targets' });
-
-    gui.add(params, 'Spherify', 0, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            mesh.morphTargetInfluences[0] = value;
-        });
-    gui.add(params, 'Twist', 0, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            mesh.morphTargetInfluences[1] = value;
-        });
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
diff --git a/examples-testing/examples/webgpu_morphtargets_face.ts b/examples-testing/examples/webgpu_morphtargets_face.ts
deleted file mode 100644
index ea9f86588..000000000
--- a/examples-testing/examples/webgpu_morphtargets_face.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-init();
-
-async function init() {
-    let mixer;
-
-    const clock = new THREE.Clock();
-
-    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 20);
-    camera.position.set(-1.8, 0.8, 3);
-
-    const scene = new THREE.Scene();
-
-    const renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    await renderer.init();
-
-    const environment = new RoomEnvironment();
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-
-    scene.background = new THREE.Color(0x666666);
-    scene.environment = pmremGenerator.fromScene(environment).texture;
-
-    const ktx2Loader = await new KTX2Loader().setTranscoderPath('jsm/libs/basis/').detectSupportAsync(renderer);
-
-    new GLTFLoader()
-        .setKTX2Loader(ktx2Loader)
-        .setMeshoptDecoder(MeshoptDecoder)
-        .load('models/gltf/facecap.glb', gltf => {
-            const mesh = gltf.scene.children[0];
-
-            scene.add(mesh);
-
-            mixer = new THREE.AnimationMixer(mesh);
-
-            mixer.clipAction(gltf.animations[0]).play();
-
-            // GUI
-
-            const head = mesh.getObjectByName('mesh_2');
-            const influences = head.morphTargetInfluences;
-
-            const gui = new GUI();
-            gui.close();
-
-            for (const [key, value] of Object.entries(head.morphTargetDictionary)) {
-                gui.add(influences, value, 0, 1, 0.01).name(key.replace('blendShape1.', '')).listen();
-            }
-        });
-
-    scene.background = new THREE.Color(0x666666);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 2.5;
-    controls.maxDistance = 5;
-    controls.minAzimuthAngle = -Math.PI / 2;
-    controls.maxAzimuthAngle = Math.PI / 2;
-    controls.maxPolarAngle = Math.PI / 1.8;
-    controls.target.set(0, 0.15, -0.2);
-
-    const stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    function animate() {
-        const delta = clock.getDelta();
-
-        if (mixer) {
-            mixer.update(delta);
-        }
-
-        renderer.render(scene, camera);
-
-        controls.update();
-
-        stats.update();
-    }
-
-    window.addEventListener('resize', () => {
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-
-        renderer.setSize(window.innerWidth, window.innerHeight);
-    });
-}
diff --git a/examples-testing/examples/webgpu_mrt.ts b/examples-testing/examples/webgpu_mrt.ts
deleted file mode 100644
index 3737c463e..000000000
--- a/examples-testing/examples/webgpu_mrt.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import * as THREE from 'three';
-import {
-    output,
-    transformedNormalView,
-    pass,
-    step,
-    diffuseColor,
-    emissive,
-    directionToColor,
-    viewportUV,
-    mix,
-    mrt,
-    Fn,
-} from 'three/tsl';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer;
-let postProcessing;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    // scene
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', function (gltf) {
-            scene.add(gltf.scene);
-        });
-    });
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    // post processing
-
-    const scenePass = pass(scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter });
-    scenePass.setMRT(
-        mrt({
-            output: output,
-            normal: directionToColor(transformedNormalView),
-            diffuse: diffuseColor,
-            emissive: emissive,
-        }),
-    );
-
-    // optimize textures
-
-    const normalTexture = scenePass.getTexture('normal');
-    const diffuseTexture = scenePass.getTexture('diffuse');
-    const emissiveTexture = scenePass.getTexture('emissive');
-
-    normalTexture.type = diffuseTexture.type = emissiveTexture.type = THREE.UnsignedByteType;
-
-    // post processing - mrt
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    postProcessing.outputColorTransform = false;
-    postProcessing.outputNode = Fn(() => {
-        const output = scenePass.getTextureNode('output'); // output name is optional here
-        const normal = scenePass.getTextureNode('normal');
-        const diffuse = scenePass.getTextureNode('diffuse');
-        const emissive = scenePass.getTextureNode('emissive');
-
-        const out = mix(output.renderOutput(), output, step(0.2, viewportUV.x));
-        const nor = mix(out, normal, step(0.4, viewportUV.x));
-        const emi = mix(nor, emissive, step(0.6, viewportUV.x));
-        const dif = mix(emi, diffuse, step(0.8, viewportUV.x));
-
-        return dif;
-    })();
-
-    // controls
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts b/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
deleted file mode 100644
index ace1533a0..000000000
--- a/examples-testing/examples/webgpu_multiple_rendertargets_readback.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import * as THREE from 'three';
-import { mix, step, texture, viewportUV, mrt, output, transformedNormalWorld, uv, vec2 } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, torus;
-let quadMesh, sceneMRT, renderTarget, readbackTarget, material, readbackMaterial, pixelBuffer, pixelBufferTexture;
-
-const gui = new GUI();
-
-const options = {
-    selection: 'mrt',
-};
-
-gui.add(options, 'selection', ['mrt', 'diffuse', 'normal']);
-
-init();
-
-function init() {
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    document.body.appendChild(renderer.domElement);
-
-    // Create a multi render target with Float buffers
-
-    renderTarget = new THREE.RenderTarget(
-        window.innerWidth * window.devicePixelRatio,
-        window.innerHeight * window.devicePixelRatio,
-        { count: 2, minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter },
-    );
-
-    // Name our G-Buffer attachments for debugging
-
-    renderTarget.textures[0].name = 'output';
-    renderTarget.textures[1].name = 'normal';
-
-    // Init readback render target, readback data texture, readback material
-    // Be careful with the size! 512 is already big. Reading data back from the GPU is computationally intensive
-
-    const size = 512;
-
-    readbackTarget = new THREE.RenderTarget(size, size, { count: 2 });
-
-    pixelBuffer = new Uint8Array(size ** 2 * 4).fill(0);
-    pixelBufferTexture = new THREE.DataTexture(pixelBuffer, size, size);
-    pixelBufferTexture.type = THREE.UnsignedByteType;
-    pixelBufferTexture.format = THREE.RGBAFormat;
-
-    readbackMaterial = new THREE.MeshBasicNodeMaterial();
-    readbackMaterial.colorNode = texture(pixelBufferTexture);
-
-    // MRT
-
-    sceneMRT = mrt({
-        output: output,
-        normal: transformedNormalWorld,
-    });
-
-    // Scene
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x222222);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 50);
-    camera.position.z = 4;
-
-    const loader = new THREE.TextureLoader();
-
-    const diffuse = loader.load('textures/hardwood2_diffuse.jpg');
-    diffuse.colorSpace = THREE.SRGBColorSpace;
-    diffuse.wrapS = THREE.RepeatWrapping;
-    diffuse.wrapT = THREE.RepeatWrapping;
-
-    const torusMaterial = new THREE.NodeMaterial();
-    torusMaterial.colorNode = texture(diffuse, uv().mul(vec2(10, 4)));
-
-    torus = new THREE.Mesh(new THREE.TorusKnotGeometry(1, 0.3, 128, 32), torusMaterial);
-    scene.add(torus);
-
-    // Output
-
-    material = new THREE.NodeMaterial();
-    material.colorNode = mix(
-        texture(renderTarget.textures[0]),
-        texture(renderTarget.textures[1]),
-        step(0.5, viewportUV.x),
-    );
-
-    quadMesh = new THREE.QuadMesh(material);
-
-    // Controls
-
-    new OrbitControls(camera, renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    const dpr = renderer.getPixelRatio();
-    renderTarget.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
-}
-
-async function render(time) {
-    const selection = options.selection;
-
-    torus.rotation.y = (time / 1000) * 0.4;
-
-    const isMRT = selection === 'mrt';
-
-    // render scene into target
-    renderer.setMRT(isMRT ? sceneMRT : null);
-    renderer.setRenderTarget(isMRT ? renderTarget : readbackTarget);
-    renderer.render(scene, camera);
-
-    // render post FX
-    renderer.setMRT(null);
-    renderer.setRenderTarget(null);
-
-    if (isMRT) {
-        quadMesh.material = material;
-    } else {
-        quadMesh.material = readbackMaterial;
-
-        await readback();
-    }
-
-    quadMesh.render(renderer);
-}
-
-async function readback() {
-    const width = readbackTarget.width;
-    const height = readbackTarget.height;
-
-    const selection = options.selection;
-
-    if (selection === 'diffuse') {
-        pixelBuffer = await renderer.readRenderTargetPixelsAsync(readbackTarget, 0, 0, width, height, 0); // zero is optional
-
-        pixelBufferTexture.image.data = pixelBuffer;
-        pixelBufferTexture.needsUpdate = true;
-    } else if (selection === 'normal') {
-        pixelBuffer = await renderer.readRenderTargetPixelsAsync(readbackTarget, 0, 0, width, height, 1);
-
-        pixelBufferTexture.image.data = pixelBuffer;
-        pixelBufferTexture.needsUpdate = true;
-    }
-}
diff --git a/examples-testing/examples/webgpu_ocean.ts b/examples-testing/examples/webgpu_ocean.ts
deleted file mode 100644
index 9eb9922dd..000000000
--- a/examples-testing/examples/webgpu_ocean.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { WaterMesh } from 'three/addons/objects/WaterMesh.js';
-import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
-
-let container, stats;
-let camera, scene, renderer;
-let controls, water, sun, mesh;
-
-init();
-
-function init() {
-    container = document.getElementById('container');
-
-    //
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 0.5;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 20000);
-    camera.position.set(30, 30, 100);
-
-    //
-
-    sun = new THREE.Vector3();
-
-    // Water
-
-    const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
-    const loader = new THREE.TextureLoader();
-    const waterNormals = loader.load('textures/waternormals.jpg');
-    waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
-
-    water = new WaterMesh(waterGeometry, {
-        waterNormals: waterNormals,
-        sunDirection: new THREE.Vector3(),
-        sunColor: 0xffffff,
-        waterColor: 0x001e0f,
-        distortionScale: 3.7,
-    });
-
-    water.rotation.x = -Math.PI / 2;
-
-    scene.add(water);
-
-    // Skybox
-
-    const sky = new SkyMesh();
-    sky.scale.setScalar(10000);
-    scene.add(sky);
-
-    sky.turbidity.value = 10;
-    sky.rayleigh.value = 2;
-    sky.mieCoefficient.value = 0.005;
-    sky.mieDirectionalG.value = 0.8;
-
-    const parameters = {
-        elevation: 2,
-        azimuth: 180,
-    };
-
-    const pmremGenerator = new THREE.PMREMGenerator(renderer);
-    const sceneEnv = new THREE.Scene();
-
-    let renderTarget;
-
-    function updateSun() {
-        const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
-        const theta = THREE.MathUtils.degToRad(parameters.azimuth);
-
-        sun.setFromSphericalCoords(1, phi, theta);
-
-        sky.sunPosition.value.copy(sun);
-        water.sunDirection.value.copy(sun).normalize();
-
-        if (renderTarget !== undefined) renderTarget.dispose();
-
-        sceneEnv.add(sky);
-        renderTarget = pmremGenerator.fromScene(sceneEnv);
-        scene.add(sky);
-
-        scene.environment = renderTarget.texture;
-    }
-
-    renderer.init().then(updateSun);
-
-    //
-
-    const geometry = new THREE.BoxGeometry(30, 30, 30);
-    const material = new THREE.MeshStandardMaterial({ roughness: 0 });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI * 0.495;
-    controls.target.set(0, 10, 0);
-    controls.minDistance = 40.0;
-    controls.maxDistance = 200.0;
-    controls.update();
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    // GUI
-
-    const gui = new GUI();
-
-    const folderSky = gui.addFolder('Sky');
-    folderSky.add(parameters, 'elevation', 0, 90, 0.1).onChange(updateSun);
-    folderSky.add(parameters, 'azimuth', -180, 180, 0.1).onChange(updateSun);
-    folderSky.open();
-
-    const folderWater = gui.addFolder('Water');
-    folderWater.add(water.distortionScale, 'value', 0, 8, 0.1).name('distortionScale');
-    folderWater.add(water.size, 'value', 0.1, 10, 0.1).name('size');
-    folderWater.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const time = performance.now() * 0.001;
-
-    mesh.position.y = Math.sin(time) * 20 + 5;
-    mesh.rotation.x = time * 0.5;
-    mesh.rotation.z = time * 0.51;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_parallax_uv.ts b/examples-testing/examples/webgpu_parallax_uv.ts
deleted file mode 100644
index 775399bfb..000000000
--- a/examples-testing/examples/webgpu_parallax_uv.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import * as THREE from 'three';
-import { texture, parallaxUV, overlay, uv } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-let controls;
-
-init();
-
-async function init() {
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 50);
-    camera.position.set(10, 14, 10);
-
-    // environment
-
-    const environmentTexture = await new THREE.CubeTextureLoader()
-        .setPath('./textures/cube/Park2/')
-        .loadAsync(['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']);
-
-    scene.environment = environmentTexture;
-    scene.background = environmentTexture;
-
-    // textures
-
-    const loader = new THREE.TextureLoader();
-
-    const topTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Color.jpg');
-    topTexture.colorSpace = THREE.SRGBColorSpace;
-
-    const roughnessTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Roughness.jpg');
-    roughnessTexture.colorSpace = THREE.NoColorSpace;
-
-    const normalTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_NormalGL.jpg');
-    normalTexture.colorSpace = THREE.NoColorSpace;
-
-    const displaceTexture = await loader.loadAsync('textures/ambientcg/Ice002_1K-JPG_Displacement.jpg');
-    displaceTexture.colorSpace = THREE.NoColorSpace;
-
-    //
-
-    const bottomTexture = await loader.loadAsync('textures/ambientcg/Ice003_1K-JPG_Color.jpg');
-    bottomTexture.colorSpace = THREE.SRGBColorSpace;
-    bottomTexture.wrapS = THREE.RepeatWrapping;
-    bottomTexture.wrapT = THREE.RepeatWrapping;
-
-    // paralax effect
-
-    const parallaxScale = 0.3;
-    const offsetUV = texture(displaceTexture).mul(parallaxScale);
-
-    const parallaxUVOffset = parallaxUV(uv(), offsetUV);
-    const parallaxResult = texture(bottomTexture, parallaxUVOffset);
-
-    const iceNode = overlay(texture(topTexture), parallaxResult);
-
-    // material
-
-    const material = new THREE.MeshStandardNodeMaterial();
-    material.colorNode = iceNode.mul(5); // increase the color intensity to 5 ( contrast )
-    material.roughnessNode = texture(roughnessTexture);
-    material.normalMap = normalTexture;
-    material.metalness = 0;
-
-    const geometry = new THREE.BoxGeometry(10, 10, 10);
-
-    const ground = new THREE.Mesh(geometry, material);
-    ground.rotateX(-Math.PI / 2);
-    scene.add(ground);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    renderer.toneMappingExposure = 6;
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0, 0);
-    controls.maxDistance = 40;
-    controls.minDistance = 10;
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = -1;
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_performance.ts b/examples-testing/examples/webgpu_performance.ts
deleted file mode 100644
index 315eba252..000000000
--- a/examples-testing/examples/webgpu_performance.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-let camera, scene, renderer, stats;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(60, 60, 60);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-
-    renderer.setAnimationLoop(render);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.environment = texture;
-
-        // model
-
-        const dracoLoader = new DRACOLoader();
-        dracoLoader.setDecoderPath('jsm/libs/draco/gltf/');
-
-        const loader = new GLTFLoader().setPath('models/gltf/');
-        loader.setDRACOLoader(dracoLoader);
-
-        loader.load('dungeon_warkarma.glb', async function (gltf) {
-            const model = gltf.scene;
-
-            // wait until the model can be added to the scene without blocking due to shader compilation
-
-            await renderer.compileAsync(model, camera, scene);
-
-            scene.add(model);
-        });
-    });
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 60;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    renderer.renderAsync(scene, camera);
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing.ts b/examples-testing/examples/webgpu_postprocessing.ts
deleted file mode 100644
index 7ae63a39d..000000000
--- a/examples-testing/examples/webgpu_postprocessing.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as THREE from 'three';
-import { pass, dotScreen, rgbShift } from 'three/tsl';
-
-let camera, renderer, postProcessing;
-let object;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    const scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 1000);
-
-    object = new THREE.Object3D();
-    scene.add(object);
-
-    const geometry = new THREE.SphereGeometry(1, 4, 4);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
-
-    for (let i = 0; i < 100; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
-        mesh.position.multiplyScalar(Math.random() * 400);
-        mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
-        mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50;
-        object.add(mesh);
-    }
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(1, 1, 1);
-    scene.add(light);
-
-    // postprocessing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    const scenePassColor = scenePass.getTextureNode();
-
-    const dotScreenPass = dotScreen(scenePassColor);
-    dotScreenPass.scale.value = 0.3;
-
-    const rgbShiftPass = rgbShift(dotScreenPass);
-    rgbShiftPass.amount.value = 0.001;
-
-    postProcessing.outputNode = rgbShiftPass;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    object.rotation.x += 0.005;
-    object.rotation.y += 0.01;
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_3dlut.ts b/examples-testing/examples/webgpu_postprocessing_3dlut.ts
deleted file mode 100644
index 27500a079..000000000
--- a/examples-testing/examples/webgpu_postprocessing_3dlut.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as THREE from 'three';
-import { pass, texture3D, uniform, lut3D, renderOutput } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js';
-import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js';
-import { LUTImageLoader } from 'three/addons/loaders/LUTImageLoader.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const params = {
-    lut: 'Bourbon 64.CUBE',
-    intensity: 1,
-};
-
-const lutMap = {
-    'Bourbon 64.CUBE': null,
-    'Chemical 168.CUBE': null,
-    'Clayton 33.CUBE': null,
-    'Cubicle 99.CUBE': null,
-    'Remy 24.CUBE': null,
-    'Presetpro-Cinematic.3dl': null,
-    NeutralLUT: null,
-    'B&WLUT': null,
-    NightLUT: null,
-};
-
-let gui;
-let camera, scene, renderer;
-let postProcessing, lutPass;
-
-init();
-
-async function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', function (gltf) {
-            scene.add(gltf.scene);
-        });
-    });
-
-    const lutCubeLoader = new LUTCubeLoader();
-    const lutImageLoader = new LUTImageLoader();
-    const lut3dlLoader = new LUT3dlLoader();
-
-    for (const name in lutMap) {
-        if (/\.CUBE$/i.test(name)) {
-            lutMap[name] = lutCubeLoader.loadAsync('luts/' + name);
-        } else if (/\LUT$/i.test(name)) {
-            lutMap[name] = lutImageLoader.loadAsync(`luts/${name}.png`);
-        } else {
-            lutMap[name] = lut3dlLoader.loadAsync('luts/' + name);
-        }
-    }
-
-    const pendings = Object.values(lutMap);
-    await Promise.all(pendings);
-
-    for (const name in lutMap) {
-        lutMap[name] = await lutMap[name];
-    }
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    // post processing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    // ignore default output color transform ( toneMapping and outputColorSpace )
-    // use renderOutput() for control the sequence
-
-    postProcessing.outputColorTransform = false;
-
-    // scene pass
-
-    const scenePass = pass(scene, camera);
-    const outputPass = renderOutput(scenePass);
-
-    const lut = lutMap[params.lut];
-    lutPass = lut3D(outputPass, texture3D(lut.texture3D), lut.texture3D.image.width, uniform(1));
-
-    postProcessing.outputNode = lutPass;
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, -0.2);
-    controls.update();
-
-    gui = new GUI();
-    gui.add(params, 'lut', Object.keys(lutMap));
-    gui.add(params, 'intensity').min(0).max(1);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    lutPass.intensityNode.value = params.intensity;
-
-    if (lutMap[params.lut]) {
-        const lut = lutMap[params.lut];
-        lutPass.lutNode.value = lut.texture3D;
-        lutPass.size.value = lut.texture3D.image.width;
-    }
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_afterimage.ts b/examples-testing/examples/webgpu_postprocessing_afterimage.ts
deleted file mode 100644
index d0775d49b..000000000
--- a/examples-testing/examples/webgpu_postprocessing_afterimage.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import * as THREE from 'three';
-import { pass, afterImage } from 'three/tsl';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-let mesh, postProcessing, combinedPass;
-
-const params = {
-    damp: 0.96,
-};
-
-init();
-createGUI();
-
-function init() {
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 400;
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x000000, 1, 1000);
-
-    const geometry = new THREE.TorusKnotGeometry(100, 30, 100, 16);
-    const material = new THREE.MeshNormalMaterial();
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    // postprocessing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    const scenePassColor = scenePass.getTextureNode();
-
-    combinedPass = scenePassColor;
-    combinedPass = afterImage(combinedPass, params.damp);
-
-    postProcessing.outputNode = combinedPass;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function createGUI() {
-    const gui = new GUI({ title: 'Damp setting' });
-    gui.add(combinedPass.damp, 'value', 0, 1).step(0.001);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function render() {
-    mesh.rotation.x += 0.0075;
-    mesh.rotation.y += 0.015;
-
-    postProcessing.render();
-}
-
-function animate() {
-    render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_ao.ts b/examples-testing/examples/webgpu_postprocessing_ao.ts
deleted file mode 100644
index 432d641a0..000000000
--- a/examples-testing/examples/webgpu_postprocessing_ao.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import * as THREE from 'three';
-import { pass, mrt, output, transformedNormalView, texture, ao, denoise } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer, postProcessing, controls, clock, stats, mixer;
-
-let aoPass, denoisePass, blendPassAO, blendPassDenoise, scenePassColor;
-
-const params = {
-    distanceExponent: 1,
-    distanceFallOff: 1,
-    radius: 0.25,
-    scale: 1,
-    thickness: 1,
-    denoised: true,
-    enabled: true,
-    denoiseRadius: 5,
-    lumaPhi: 5,
-    depthPhi: 5,
-    normalPhi: 5,
-};
-
-init();
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(5, 2, 8);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xbfe3dd);
-
-    clock = new THREE.Clock();
-
-    const hdrloader = new RGBELoader();
-    const envMap = await hdrloader.loadAsync('textures/equirectangular/quarry_01_1k.hdr');
-    envMap.mapping = THREE.EquirectangularReflectionMapping;
-
-    scene.environment = envMap;
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.target.set(0, 0.5, 0);
-    controls.update();
-    controls.enablePan = false;
-    controls.enableDamping = true;
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    //
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    scenePass.setMRT(
-        mrt({
-            output: output,
-            normal: transformedNormalView,
-        }),
-    );
-
-    scenePassColor = scenePass.getTextureNode('output');
-    const scenePassNormal = scenePass.getTextureNode('normal');
-    const scenePassDepth = scenePass.getTextureNode('depth');
-
-    // ao
-
-    aoPass = ao(scenePassDepth, scenePassNormal, camera);
-    blendPassAO = aoPass.getTextureNode().mul(scenePassColor);
-
-    // denoise (optional)
-
-    const noiseTexture = texture(generateNoise());
-    denoisePass = denoise(aoPass.getTextureNode(), scenePassDepth, scenePassNormal, noiseTexture, camera);
-    blendPassDenoise = denoisePass.mul(scenePassColor);
-
-    postProcessing.outputNode = blendPassDenoise;
-
-    //
-
-    const dracoLoader = new DRACOLoader();
-    dracoLoader.setDecoderPath('jsm/libs/draco/');
-    dracoLoader.setDecoderConfig({ type: 'js' });
-    const loader = new GLTFLoader();
-    loader.setDRACOLoader(dracoLoader);
-    loader.setPath('models/gltf/');
-
-    const gltf = await loader.loadAsync('LittlestTokyo.glb');
-
-    const model = gltf.scene;
-    model.position.set(1, 1, 0);
-    model.scale.set(0.01, 0.01, 0.01);
-    scene.add(model);
-
-    mixer = new THREE.AnimationMixer(model);
-    mixer.clipAction(gltf.animations[0]).play();
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.title('AO settings');
-    gui.add(params, 'distanceExponent').min(1).max(4).onChange(updateParameters);
-    gui.add(params, 'distanceFallOff').min(0.01).max(1).onChange(updateParameters);
-    gui.add(params, 'radius').min(0.01).max(1).onChange(updateParameters);
-    gui.add(params, 'scale').min(0.01).max(2).onChange(updateParameters);
-    gui.add(params, 'thickness').min(0.01).max(2).onChange(updateParameters);
-    gui.add(params, 'denoised').onChange(updatePassChain);
-    gui.add(params, 'enabled').onChange(updatePassChain);
-    const folder = gui.addFolder('Denoise settings');
-    folder.add(params, 'denoiseRadius').min(0.01).max(10).name('radius').onChange(updateParameters);
-    folder.add(params, 'lumaPhi').min(0.01).max(10).onChange(updateParameters);
-    folder.add(params, 'depthPhi').min(0.01).max(10).onChange(updateParameters);
-    folder.add(params, 'normalPhi').min(0.01).max(10).onChange(updateParameters);
-}
-
-function updatePassChain() {
-    if (params.enabled === true) {
-        if (params.denoised === true) {
-            postProcessing.outputNode = blendPassDenoise;
-        } else {
-            postProcessing.outputNode = blendPassAO;
-        }
-    } else {
-        postProcessing.outputNode = scenePassColor;
-    }
-
-    postProcessing.needsUpdate = true;
-}
-
-function updateParameters() {
-    aoPass.distanceExponent.value = params.distanceExponent;
-    aoPass.distanceFallOff.value = params.distanceFallOff;
-    aoPass.radius.value = params.radius;
-    aoPass.scale.value = params.scale;
-    aoPass.thickness.value = params.thickness;
-
-    denoisePass.radius.value = params.denoiseRadius;
-    denoisePass.lumaPhi.value = params.lumaPhi;
-    denoisePass.depthPhi.value = params.depthPhi;
-    denoisePass.normalPhi.value = params.normalPhi;
-}
-
-function generateNoise(size = 64) {
-    const simplex = new SimplexNoise();
-
-    const arraySize = size * size * 4;
-    const data = new Uint8Array(arraySize);
-
-    for (let i = 0; i < size; i++) {
-        for (let j = 0; j < size; j++) {
-            const x = i;
-            const y = j;
-
-            data[(i * size + j) * 4] = (simplex.noise(x, y) * 0.5 + 0.5) * 255;
-            data[(i * size + j) * 4 + 1] = (simplex.noise(x + size, y) * 0.5 + 0.5) * 255;
-            data[(i * size + j) * 4 + 2] = (simplex.noise(x, y + size) * 0.5 + 0.5) * 255;
-            data[(i * size + j) * 4 + 3] = (simplex.noise(x + size, y + size) * 0.5 + 0.5) * 255;
-        }
-    }
-
-    const noiseTexture = new THREE.DataTexture(data, size, size);
-    noiseTexture.wrapS = THREE.RepeatWrapping;
-    noiseTexture.wrapT = THREE.RepeatWrapping;
-    noiseTexture.needsUpdate = true;
-
-    return noiseTexture;
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (mixer) {
-        mixer.update(delta);
-    }
-
-    controls.update();
-
-    postProcessing.render();
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom.ts b/examples-testing/examples/webgpu_postprocessing_bloom.ts
deleted file mode 100644
index d38a7abb1..000000000
--- a/examples-testing/examples/webgpu_postprocessing_bloom.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import * as THREE from 'three';
-import { pass, bloom } from 'three/tsl';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, stats;
-let postProcessing, renderer, mixer, clock;
-
-const params = {
-    threshold: 0,
-    strength: 1,
-    radius: 0,
-    exposure: 1,
-};
-
-init();
-
-async function init() {
-    const container = document.getElementById('container');
-
-    clock = new THREE.Clock();
-
-    const scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(-5, 2.5, -3.5);
-    scene.add(camera);
-
-    scene.add(new THREE.AmbientLight(0xcccccc));
-
-    const pointLight = new THREE.PointLight(0xffffff, 100);
-    camera.add(pointLight);
-
-    const loader = new GLTFLoader();
-    const gltf = await loader.loadAsync('models/gltf/PrimaryIonDrive.glb');
-
-    const model = gltf.scene;
-    scene.add(model);
-
-    mixer = new THREE.AnimationMixer(model);
-    const clip = gltf.animations[0];
-    mixer.clipAction(clip.optimize()).play();
-
-    //
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ReinhardToneMapping;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    const scenePassColor = scenePass.getTextureNode('output');
-
-    const bloomPass = bloom(scenePassColor);
-
-    postProcessing.outputNode = scenePassColor.add(bloomPass);
-
-    //
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxPolarAngle = Math.PI * 0.5;
-    controls.minDistance = 3;
-    controls.maxDistance = 8;
-
-    //
-
-    const gui = new GUI();
-
-    const bloomFolder = gui.addFolder('bloom');
-
-    bloomFolder.add(params, 'threshold', 0.0, 1.0).onChange(function (value) {
-        bloomPass.threshold.value = value;
-    });
-
-    bloomFolder.add(params, 'strength', 0.0, 3.0).onChange(function (value) {
-        bloomPass.strength.value = value;
-    });
-
-    gui.add(params, 'radius', 0.0, 1.0)
-        .step(0.01)
-        .onChange(function (value) {
-            bloomPass.radius.value = value;
-        });
-
-    const toneMappingFolder = gui.addFolder('tone mapping');
-
-    toneMappingFolder.add(params, 'exposure', 0.1, 2).onChange(function (value) {
-        renderer.toneMappingExposure = Math.pow(value, 4.0);
-    });
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    mixer.update(delta);
-
-    postProcessing.render();
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts b/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
deleted file mode 100644
index 7a6569f94..000000000
--- a/examples-testing/examples/webgpu_postprocessing_bloom_emissive.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import * as THREE from 'three';
-import { pass, mrt, output, bloom, emissive } from 'three/tsl';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-let postProcessing;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
-    camera.position.set(-1.8, 0.6, 2.7);
-
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('moonless_golf_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-
-        // model
-
-        const loader = new GLTFLoader().setPath('models/gltf/DamagedHelmet/glTF/');
-        loader.load('DamagedHelmet.gltf', function (gltf) {
-            scene.add(gltf.scene);
-        });
-    });
-
-    //
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const scenePass = pass(scene, camera);
-    scenePass.setMRT(
-        mrt({
-            output,
-            emissive,
-        }),
-    );
-
-    const outputPass = scenePass.getTextureNode();
-    const emissivePass = scenePass.getTextureNode('emissive');
-
-    const bloomPass = bloom(emissivePass, 2.5, 0.5);
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    postProcessing.outputNode = outputPass.add(bloomPass);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.target.set(0, 0, -0.2);
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-
-    const bloomFolder = gui.addFolder('bloom');
-    bloomFolder.add(bloomPass.strength, 'value', 0.0, 5.0).name('strength');
-    bloomFolder.add(bloomPass.radius, 'value', 0.0, 1.0).name('radius');
-
-    const toneMappingFolder = gui.addFolder('tone mapping');
-    toneMappingFolder.add(renderer, 'toneMappingExposure', 0.1, 2).name('exposure');
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function render() {
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts b/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
deleted file mode 100644
index 55d6b6bbe..000000000
--- a/examples-testing/examples/webgpu_postprocessing_bloom_selective.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import * as THREE from 'three';
-import { pass, mrt, output, float, bloom, uniform } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-// scene
-
-const scene = new THREE.Scene();
-
-const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 200);
-camera.position.set(0, 0, 20);
-camera.lookAt(0, 0, 0);
-
-const geometry = new THREE.IcosahedronGeometry(1, 15);
-
-for (let i = 0; i < 50; i++) {
-    const color = new THREE.Color();
-    color.setHSL(Math.random(), 0.7, Math.random() * 0.2 + 0.05);
-
-    const bloomIntensity = Math.random() > 0.5 ? 1 : 0;
-
-    const material = new THREE.MeshBasicNodeMaterial({ color: color });
-    material.mrtNode = mrt({
-        bloomIntensity: uniform(bloomIntensity),
-    });
-
-    const sphere = new THREE.Mesh(geometry, material);
-    sphere.position.x = Math.random() * 10 - 5;
-    sphere.position.y = Math.random() * 10 - 5;
-    sphere.position.z = Math.random() * 10 - 5;
-    sphere.position.normalize().multiplyScalar(Math.random() * 4.0 + 2.0);
-    sphere.scale.setScalar(Math.random() * Math.random() + 0.5);
-    scene.add(sphere);
-}
-
-// renderer
-
-const renderer = new THREE.WebGPURenderer();
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.setAnimationLoop(animate);
-renderer.toneMapping = THREE.NeutralToneMapping;
-document.body.appendChild(renderer.domElement);
-
-// post processing
-
-const scenePass = pass(scene, camera);
-scenePass.setMRT(
-    mrt({
-        output,
-        bloomIntensity: float(0), // default bloom intensity
-    }),
-);
-
-const outputPass = scenePass.getTextureNode();
-const bloomIntensityPass = scenePass.getTextureNode('bloomIntensity');
-
-const bloomPass = bloom(outputPass.mul(bloomIntensityPass));
-
-const postProcessing = new THREE.PostProcessing(renderer);
-postProcessing.outputColorTransform = false;
-postProcessing.outputNode = outputPass.add(bloomPass).renderOutput();
-
-// controls
-
-const controls = new OrbitControls(camera, renderer.domElement);
-controls.maxPolarAngle = Math.PI * 0.5;
-controls.minDistance = 1;
-controls.maxDistance = 100;
-
-// raycaster
-
-const raycaster = new THREE.Raycaster();
-const mouse = new THREE.Vector2();
-
-window.addEventListener('pointerdown', event => {
-    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
-    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
-
-    raycaster.setFromCamera(mouse, camera);
-
-    const intersects = raycaster.intersectObjects(scene.children, false);
-
-    if (intersects.length > 0) {
-        const material = intersects[0].object.material;
-
-        const bloomIntensity = material.mrtNode.get('bloomIntensity');
-        bloomIntensity.value = bloomIntensity.value === 0 ? 1 : 0;
-    }
-});
-
-// gui
-
-const gui = new GUI();
-
-const bloomFolder = gui.addFolder('bloom');
-bloomFolder.add(bloomPass.threshold, 'value', 0.0, 1.0).name('threshold');
-bloomFolder.add(bloomPass.strength, 'value', 0.0, 3).name('strength');
-bloomFolder.add(bloomPass.radius, 'value', 0.0, 1.0).name('radius');
-
-const toneMappingFolder = gui.addFolder('tone mapping');
-toneMappingFolder.add(renderer, 'toneMappingExposure', 0.1, 3).name('exposure');
-
-// events
-
-window.onresize = function () {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-};
-
-// animate
-
-function animate() {
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_difference.ts b/examples-testing/examples/webgpu_postprocessing_difference.ts
deleted file mode 100644
index dc30eb604..000000000
--- a/examples-testing/examples/webgpu_postprocessing_difference.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as THREE from 'three';
-import { pass, luminance, saturation } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { Timer } from 'three/addons/misc/Timer.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const params = {
-    speed: 0,
-};
-
-let camera, renderer, postProcessing;
-let timer, mesh, controls;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.NeutralToneMapping;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 100);
-    camera.position.set(1, 2, 3);
-
-    const scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x0487e2, 7, 25);
-    scene.background = new THREE.Color(0x0487e2);
-
-    timer = new Timer();
-
-    const texture = new THREE.TextureLoader().load('textures/crate.gif');
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    const geometry = new THREE.BoxGeometry();
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    // post processing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-
-    const currentTexture = scenePass.getTextureNode();
-    const previousTexture = scenePass.getPreviousTextureNode();
-
-    const frameDiff = previousTexture.sub(currentTexture).abs();
-
-    const saturationAmount = luminance(frameDiff).mul(1000).clamp(0, 3);
-
-    postProcessing.outputNode = saturation(currentTexture, saturationAmount);
-
-    //
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 2;
-    controls.maxDistance = 10;
-    controls.enableDamping = true;
-    controls.dampingFactor = 0.01;
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.add(params, 'speed', 0, 2);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    timer.update();
-
-    controls.update();
-
-    mesh.rotation.y += timer.getDelta() * 5 * params.speed;
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_dof.ts b/examples-testing/examples/webgpu_postprocessing_dof.ts
deleted file mode 100644
index 26d034fba..000000000
--- a/examples-testing/examples/webgpu_postprocessing_dof.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import * as THREE from 'three';
-import { cubeTexture, positionWorld, oscSine, timerGlobal, pass, dof, uniform } from 'three/tsl';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-//
-
-let camera, scene, renderer, mesh, stats;
-
-let mouseX = 0,
-    mouseY = 0;
-
-let windowHalfX = window.innerWidth / 2;
-let windowHalfY = window.innerHeight / 2;
-
-let width = window.innerWidth;
-let height = window.innerHeight;
-
-let postProcessing;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, width / height, 1, 3000);
-    camera.position.z = 200;
-
-    scene = new THREE.Scene();
-
-    const path = 'textures/cube/SwedishRoyalCastle/';
-    const format = '.jpg';
-    const urls = [
-        path + 'px' + format,
-        path + 'nx' + format,
-        path + 'py' + format,
-        path + 'ny' + format,
-        path + 'pz' + format,
-        path + 'nz' + format,
-    ];
-
-    const xgrid = 14,
-        ygrid = 9,
-        zgrid = 14;
-    const count = xgrid * ygrid * zgrid;
-
-    const textureCube = new THREE.CubeTextureLoader().load(urls);
-    const cubeTextureNode = cubeTexture(textureCube);
-    const oscPos = oscSine(positionWorld.div(1000 /* scene distance */).add(timerGlobal(0.2 /* speed */)));
-
-    const geometry = new THREE.SphereGeometry(60, 20, 10);
-    const material = new THREE.MeshBasicNodeMaterial();
-    material.colorNode = cubeTextureNode.mul(oscPos);
-
-    mesh = new THREE.InstancedMesh(geometry, material, count);
-    scene.add(mesh);
-
-    const matrix = new THREE.Matrix4();
-
-    let index = 0;
-
-    for (let i = 0; i < xgrid; i++) {
-        for (let j = 0; j < ygrid; j++) {
-            for (let k = 0; k < zgrid; k++) {
-                const x = 200 * (i - xgrid / 2);
-                const y = 200 * (j - ygrid / 2);
-                const z = 200 * (k - zgrid / 2);
-
-                mesh.setMatrixAt(index, matrix.identity().setPosition(x, y, z));
-                index++;
-            }
-        }
-    }
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    const effectController = {
-        focus: uniform(500.0),
-        aperture: uniform(5),
-        maxblur: uniform(0.01),
-    };
-
-    // post processing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-
-    const scenePassColor = scenePass.getTextureNode();
-    const scenePassViewZ = scenePass.getViewZNode();
-
-    const dofPass = dof(
-        scenePassColor,
-        scenePassViewZ,
-        effectController.focus,
-        effectController.aperture.mul(0.00001),
-        effectController.maxblur,
-    );
-
-    postProcessing.outputNode = dofPass;
-
-    // controls
-
-    renderer.domElement.style.touchAction = 'none';
-    renderer.domElement.addEventListener('pointermove', onPointerMove);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // stats
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    // gui
-
-    const gui = new GUI();
-    gui.add(effectController.focus, 'value', 10.0, 3000.0, 10).name('focus');
-    gui.add(effectController.aperture, 'value', 0, 10, 0.1).name('aperture');
-    gui.add(effectController.maxblur, 'value', 0.0, 0.01, 0.001).name('maxblur');
-}
-
-function onPointerMove(event) {
-    if (event.isPrimary === false) return;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-function onWindowResize() {
-    windowHalfX = window.innerWidth / 2;
-    windowHalfY = window.innerHeight / 2;
-
-    width = window.innerWidth;
-    height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    render();
-    //stats.update();
-}
-
-function render() {
-    camera.position.x += (mouseX - camera.position.x) * 0.036;
-    camera.position.y += (-mouseY - camera.position.y) * 0.036;
-
-    camera.lookAt(scene.position);
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_fxaa.ts b/examples-testing/examples/webgpu_postprocessing_fxaa.ts
deleted file mode 100644
index 3221614b7..000000000
--- a/examples-testing/examples/webgpu_postprocessing_fxaa.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import * as THREE from 'three';
-import { pass, fxaa, renderOutput } from 'three/tsl';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-const params = {
-    enabled: true,
-    animated: false,
-};
-
-let camera, scene, renderer, clock, group;
-let postProcessing;
-
-init();
-
-async function init() {
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
-    camera.position.z = 50;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0xffffff);
-
-    clock = new THREE.Clock();
-
-    //
-
-    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d);
-    hemiLight.position.set(0, 1000, 0);
-    scene.add(hemiLight);
-
-    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
-    dirLight.position.set(-3000, 1000, -1000);
-    scene.add(dirLight);
-
-    //
-
-    group = new THREE.Group();
-
-    const geometry = new THREE.TetrahedronGeometry();
-    const material = new THREE.MeshStandardMaterial({ color: 0xf73232, flatShading: true });
-
-    for (let i = 0; i < 100; i++) {
-        const mesh = new THREE.Mesh(geometry, material);
-
-        mesh.position.x = Math.random() * 50 - 25;
-        mesh.position.y = Math.random() * 50 - 25;
-        mesh.position.z = Math.random() * 50 - 25;
-
-        mesh.scale.setScalar(Math.random() * 2 + 1);
-
-        mesh.rotation.x = Math.random() * Math.PI;
-        mesh.rotation.y = Math.random() * Math.PI;
-        mesh.rotation.z = Math.random() * Math.PI;
-
-        group.add(mesh);
-    }
-
-    scene.add(group);
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // post processing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    // ignore default output color transform ( toneMapping and outputColorSpace )
-    // use renderOutput() for control the sequence
-
-    postProcessing.outputColorTransform = false;
-
-    // scene pass
-
-    const scenePass = pass(scene, camera);
-    const outputPass = renderOutput(scenePass);
-
-    // FXAA must be computed in sRGB color space (so after tone mapping and color space conversion)
-
-    const fxaaPass = fxaa(outputPass);
-    postProcessing.outputNode = fxaaPass;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const gui = new GUI();
-    gui.title('FXAA settings');
-    gui.add(params, 'enabled').onChange(value => {
-        if (value === true) {
-            postProcessing.outputNode = fxaaPass;
-        } else {
-            postProcessing.outputNode = outputPass;
-        }
-
-        postProcessing.needsUpdate = true;
-    });
-    gui.add(params, 'animated');
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    const delta = clock.getDelta();
-
-    if (params.animated === true) {
-        group.rotation.y += delta * 0.1;
-    }
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_masking.ts b/examples-testing/examples/webgpu_postprocessing_masking.ts
deleted file mode 100644
index a4f56b170..000000000
--- a/examples-testing/examples/webgpu_postprocessing_masking.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as THREE from 'three';
-import { pass, texture } from 'three/tsl';
-
-let camera, postProcessing, renderer;
-let box, torus;
-
-init();
-
-function init() {
-    // scene
-
-    const baseScene = new THREE.Scene();
-    baseScene.background = new THREE.Color(0xe0e0e0);
-
-    const maskScene1 = new THREE.Scene();
-    box = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 4));
-    maskScene1.add(box);
-
-    const maskScene2 = new THREE.Scene();
-    torus = new THREE.Mesh(new THREE.TorusGeometry(3, 1, 16, 32));
-    maskScene2.add(torus);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.position.z = 10;
-
-    // textures
-
-    const texture1 = new THREE.TextureLoader().load('textures/758px-Canestra_di_frutta_(Caravaggio).jpg');
-    texture1.colorSpace = THREE.SRGBColorSpace;
-    texture1.minFilter = THREE.LinearFilter;
-    texture1.flipY = false;
-
-    const texture2 = new THREE.TextureLoader().load('textures/2294472375_24a3b8ef46_o.jpg');
-    texture2.colorSpace = THREE.SRGBColorSpace;
-    texture2.flipY = false;
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // post processing
-
-    const base = pass(baseScene, camera);
-    const sceneMask1 = pass(maskScene1, camera).a;
-    const sceneMask2 = pass(maskScene2, camera).a;
-
-    let compose = base;
-    compose = sceneMask1.mix(compose, texture(texture1));
-    compose = sceneMask2.mix(compose, texture(texture2));
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    postProcessing.outputNode = compose;
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    const time = performance.now() * 0.001 + 6000;
-
-    box.position.x = Math.cos(time / 1.5) * 2;
-    box.position.y = Math.sin(time) * 2;
-    box.rotation.x = time;
-    box.rotation.y = time / 2;
-
-    torus.position.x = Math.cos(time) * 2;
-    torus.position.y = Math.sin(time / 1.5) * 2;
-    torus.rotation.x = time;
-    torus.rotation.y = time / 2;
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_motion_blur.ts b/examples-testing/examples/webgpu_postprocessing_motion_blur.ts
deleted file mode 100644
index 7e86bb483..000000000
--- a/examples-testing/examples/webgpu_postprocessing_motion_blur.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-import * as THREE from 'three';
-import { pass, texture, motionBlur, uniform, output, mrt, mix, velocity, uv, viewportUV } from 'three/tsl';
-
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer;
-let boxLeft, boxRight, model, mixer, clock;
-let postProcessing;
-let controls;
-let stats;
-
-const params = {
-    speed: 1.0,
-};
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.25, 30);
-    camera.position.set(0, 1.5, 4.5);
-
-    scene = new THREE.Scene();
-    scene.fog = new THREE.Fog(0x0487e2, 7, 25);
-
-    const sunLight = new THREE.DirectionalLight(0xffe499, 5);
-    sunLight.castShadow = true;
-    sunLight.shadow.camera.near = 0.1;
-    sunLight.shadow.camera.far = 10;
-    sunLight.shadow.camera.right = 2;
-    sunLight.shadow.camera.left = -2;
-    sunLight.shadow.camera.top = 2;
-    sunLight.shadow.camera.bottom = -2;
-    sunLight.shadow.mapSize.width = 2048;
-    sunLight.shadow.mapSize.height = 2048;
-    sunLight.shadow.bias = -0.001;
-    sunLight.position.set(4, 4, 2);
-
-    const waterAmbientLight = new THREE.HemisphereLight(0x333366, 0x74ccf4, 5);
-    const skyAmbientLight = new THREE.HemisphereLight(0x74ccf4, 0, 1);
-
-    scene.add(sunLight);
-    scene.add(skyAmbientLight);
-    scene.add(waterAmbientLight);
-
-    clock = new THREE.Clock();
-
-    // animated model
-
-    const loader = new GLTFLoader();
-    loader.load('models/gltf/Xbot.glb', function (gltf) {
-        model = gltf.scene;
-
-        model.rotation.y = Math.PI / 2;
-
-        model.traverse(function (child) {
-            if (child.isMesh) {
-                child.castShadow = true;
-                child.receiveShadow = true;
-            }
-        });
-
-        mixer = new THREE.AnimationMixer(model);
-
-        const action = mixer.clipAction(gltf.animations[3]);
-        action.play();
-
-        scene.add(model);
-    });
-
-    // textures
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const floorColor = textureLoader.load('textures/floors/FloorsCheckerboard_S_Diffuse.jpg');
-    floorColor.wrapS = THREE.RepeatWrapping;
-    floorColor.wrapT = THREE.RepeatWrapping;
-    floorColor.colorSpace = THREE.SRGBColorSpace;
-
-    const floorNormal = textureLoader.load('textures/floors/FloorsCheckerboard_S_Normal.jpg');
-    floorNormal.wrapS = THREE.RepeatWrapping;
-    floorNormal.wrapT = THREE.RepeatWrapping;
-
-    // floor
-
-    const floorUV = uv().mul(5);
-
-    const floorMaterial = new THREE.MeshPhongNodeMaterial();
-    floorMaterial.colorNode = texture(floorColor, floorUV);
-
-    const floor = new THREE.Mesh(new THREE.BoxGeometry(15, 0.001, 15), floorMaterial);
-    floor.receiveShadow = true;
-
-    floor.position.set(0, 0, 0);
-    scene.add(floor);
-
-    const walls = new THREE.Mesh(
-        new THREE.BoxGeometry(15, 15, 15),
-        new THREE.MeshPhongNodeMaterial({ colorNode: floorMaterial.colorNode, side: THREE.BackSide }),
-    );
-    scene.add(walls);
-
-    const map = new THREE.TextureLoader().load('textures/uv_grid_opengl.jpg');
-    map.colorSpace = THREE.SRGBColorSpace;
-
-    const geometry = new THREE.TorusGeometry(0.8);
-    const material = new THREE.MeshBasicMaterial({ map });
-
-    boxRight = new THREE.Mesh(geometry, material);
-    boxRight.position.set(3.5, 1.5, -4);
-    scene.add(boxRight);
-
-    boxLeft = new THREE.Mesh(geometry, material);
-    boxLeft.position.set(-3.5, 1.5, -4);
-    scene.add(boxLeft);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 1;
-    controls.maxDistance = 10;
-    controls.maxPolarAngle = Math.PI / 2;
-    controls.autoRotate = true;
-    controls.autoRotateSpeed = 1;
-    controls.target.set(0, 1, 0);
-    controls.enableDamping = true;
-    controls.dampingFactor = 0.05;
-    controls.update();
-
-    // post-processing
-
-    const blurAmount = uniform(1);
-    const showVelocity = uniform(0);
-
-    const scenePass = pass(scene, camera);
-
-    scenePass.setMRT(
-        mrt({
-            output,
-            velocity,
-        }),
-    );
-
-    const beauty = scenePass.getTextureNode();
-    const vel = scenePass.getTextureNode('velocity').mul(blurAmount);
-
-    const mBlur = motionBlur(beauty, vel);
-
-    const vignet = viewportUV.distance(0.5).remap(0.6, 1).mul(2).clamp().oneMinus();
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    postProcessing.outputNode = mix(mBlur, vel, showVelocity).mul(vignet);
-
-    //
-
-    const gui = new GUI();
-    gui.title('Motion Blur Settings');
-    gui.add(controls, 'autoRotate');
-    gui.add(blurAmount, 'value', 0, 3).name('blur amount');
-    gui.add(params, 'speed', 0, 2);
-    gui.add(showVelocity, 'value', 0, 1).name('show velocity');
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    stats.update();
-
-    controls.update();
-
-    const delta = clock.getDelta();
-    const speed = params.speed;
-
-    boxRight.rotation.y += delta * 4 * speed;
-    boxLeft.scale.setScalar(1 + Math.sin(clock.elapsedTime * 10 * speed) * 0.2);
-
-    if (model) {
-        mixer.update(delta * speed);
-    }
-
-    postProcessing.render();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_pixel.ts b/examples-testing/examples/webgpu_postprocessing_pixel.ts
deleted file mode 100644
index d7e51008d..000000000
--- a/examples-testing/examples/webgpu_postprocessing_pixel.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import * as THREE from 'three';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-import { uniform, pixelationPass } from 'three/tsl';
-
-let camera, scene, renderer, postProcessing, crystalMesh, clock;
-let gui, effectController;
-
-init();
-
-function init() {
-    const aspectRatio = window.innerWidth / window.innerHeight;
-
-    camera = new THREE.OrthographicCamera(-aspectRatio, aspectRatio, 1, -1, 0.1, 10);
-    camera.position.y = 2 * Math.tan(Math.PI / 6);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x151729);
-
-    clock = new THREE.Clock();
-
-    // textures
-
-    const loader = new THREE.TextureLoader();
-    const texChecker = pixelTexture(loader.load('textures/checker.png'));
-    const texChecker2 = pixelTexture(loader.load('textures/checker.png'));
-    texChecker.repeat.set(3, 3);
-    texChecker2.repeat.set(1.5, 1.5);
-
-    // meshes
-
-    const boxMaterial = new THREE.MeshPhongMaterial({ map: texChecker2 });
-
-    function addBox(boxSideLength, x, z, rotation) {
-        const mesh = new THREE.Mesh(new THREE.BoxGeometry(boxSideLength, boxSideLength, boxSideLength), boxMaterial);
-        mesh.castShadow = true;
-        mesh.receiveShadow = true;
-        mesh.rotation.y = rotation;
-        mesh.position.y = boxSideLength / 2;
-        mesh.position.set(x, boxSideLength / 2 + 0.0001, z);
-        scene.add(mesh);
-        return mesh;
-    }
-
-    addBox(0.4, 0, 0, Math.PI / 4);
-    addBox(0.5, -0.5, -0.5, Math.PI / 4);
-
-    const planeSideLength = 2;
-    const planeMesh = new THREE.Mesh(
-        new THREE.PlaneGeometry(planeSideLength, planeSideLength),
-        new THREE.MeshPhongMaterial({ map: texChecker }),
-    );
-    planeMesh.receiveShadow = true;
-    planeMesh.rotation.x = -Math.PI / 2;
-    scene.add(planeMesh);
-
-    const radius = 0.2;
-    const geometry = new THREE.IcosahedronGeometry(radius);
-    crystalMesh = new THREE.Mesh(
-        geometry,
-        new THREE.MeshPhongMaterial({
-            color: 0x68b7e9,
-            emissive: 0x4f7e8b,
-            shininess: 10,
-            specular: 0xffffff,
-        }),
-    );
-    crystalMesh.receiveShadow = true;
-    crystalMesh.castShadow = true;
-    scene.add(crystalMesh);
-
-    // lights
-
-    scene.add(new THREE.AmbientLight(0x757f8e, 3));
-
-    const directionalLight = new THREE.DirectionalLight(0xfffecd, 1.5);
-    directionalLight.position.set(100, 100, 100);
-    directionalLight.castShadow = true;
-    directionalLight.shadow.mapSize.set(2048, 2048);
-    directionalLight.shadow.bias = -0.0001;
-    scene.add(directionalLight);
-
-    const spotLight = new THREE.SpotLight(0xffc100, 10, 10, Math.PI / 16, 0.02, 2);
-    spotLight.position.set(2, 2, 0);
-    const target = spotLight.target;
-    scene.add(target);
-    target.position.set(0, 0, 0);
-    spotLight.castShadow = true;
-    spotLight.shadow.bias = -0.001;
-    scene.add(spotLight);
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.shadowMap.enabled = true;
-    renderer.shadowMap.type = THREE.BasicShadowMap;
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    effectController = {
-        pixelSize: uniform(6),
-        normalEdgeStrength: uniform(0.3),
-        depthEdgeStrength: uniform(0.4),
-        pixelAlignedPanning: true,
-    };
-
-    postProcessing = new THREE.PostProcessing(renderer);
-    const scenePass = pixelationPass(
-        scene,
-        camera,
-        effectController.pixelSize,
-        effectController.normalEdgeStrength,
-        effectController.depthEdgeStrength,
-    );
-    postProcessing.outputNode = scenePass;
-
-    window.addEventListener('resize', onWindowResize);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.maxZoom = 2;
-
-    // gui
-
-    gui = new GUI();
-    gui.add(effectController.pixelSize, 'value', 1, 16, 1).name('Pixel Size');
-    gui.add(effectController.normalEdgeStrength, 'value', 0, 2, 0.05).name('Normal Edge Strength');
-    gui.add(effectController.depthEdgeStrength, 'value', 0, 1, 0.05).name('Depth Edge Strength');
-    gui.add(effectController, 'pixelAlignedPanning');
-}
-
-function onWindowResize() {
-    const aspectRatio = window.innerWidth / window.innerHeight;
-    camera.left = -aspectRatio;
-    camera.right = aspectRatio;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const t = clock.getElapsedTime();
-
-    crystalMesh.material.emissiveIntensity = Math.sin(t * 3) * 0.5 + 0.5;
-    crystalMesh.position.y = 0.7 + Math.sin(t * 2) * 0.05;
-    crystalMesh.rotation.y = stopGoEased(t, 2, 4) * 2 * Math.PI;
-
-    const rendererSize = renderer.getSize(new THREE.Vector2());
-    const aspectRatio = rendererSize.x / rendererSize.y;
-
-    if (effectController.pixelAlignedPanning) {
-        const pixelSize = effectController.pixelSize.value;
-
-        pixelAlignFrustum(
-            camera,
-            aspectRatio,
-            Math.floor(rendererSize.x / pixelSize),
-            Math.floor(rendererSize.y / pixelSize),
-        );
-    } else if (camera.left != -aspectRatio || camera.top != 1.0) {
-        // Reset the Camera Frustum if it has been modified
-        camera.left = -aspectRatio;
-        camera.right = aspectRatio;
-        camera.top = 1.0;
-        camera.bottom = -1.0;
-        camera.updateProjectionMatrix();
-    }
-
-    postProcessing.render();
-}
-
-// Helper functions
-
-function pixelTexture(texture) {
-    texture.minFilter = THREE.NearestFilter;
-    texture.magFilter = THREE.NearestFilter;
-    texture.generateMipmaps = false;
-    texture.wrapS = THREE.RepeatWrapping;
-    texture.wrapT = THREE.RepeatWrapping;
-    texture.colorSpace = THREE.SRGBColorSpace;
-    return texture;
-}
-
-function easeInOutCubic(x) {
-    return x ** 2 * 3 - x ** 3 * 2;
-}
-
-function linearStep(x, edge0, edge1) {
-    const w = edge1 - edge0;
-    const m = 1 / w;
-    const y0 = -m * edge0;
-    return THREE.MathUtils.clamp(y0 + m * x, 0, 1);
-}
-
-function stopGoEased(x, downtime, period) {
-    const cycle = (x / period) | 0;
-    const tween = x - cycle * period;
-    const linStep = easeInOutCubic(linearStep(tween, downtime, period));
-    return cycle + linStep;
-}
-
-function pixelAlignFrustum(camera, aspectRatio, pixelsPerScreenWidth, pixelsPerScreenHeight) {
-    // 0. Get Pixel Grid Units
-    const worldScreenWidth = (camera.right - camera.left) / camera.zoom;
-    const worldScreenHeight = (camera.top - camera.bottom) / camera.zoom;
-    const pixelWidth = worldScreenWidth / pixelsPerScreenWidth;
-    const pixelHeight = worldScreenHeight / pixelsPerScreenHeight;
-
-    // 1. Project the current camera position along its local rotation bases
-    const camPos = new THREE.Vector3();
-    camera.getWorldPosition(camPos);
-    const camRot = new THREE.Quaternion();
-    camera.getWorldQuaternion(camRot);
-    const camRight = new THREE.Vector3(1.0, 0.0, 0.0).applyQuaternion(camRot);
-    const camUp = new THREE.Vector3(0.0, 1.0, 0.0).applyQuaternion(camRot);
-    const camPosRight = camPos.dot(camRight);
-    const camPosUp = camPos.dot(camUp);
-
-    // 2. Find how far along its position is along these bases in pixel units
-    const camPosRightPx = camPosRight / pixelWidth;
-    const camPosUpPx = camPosUp / pixelHeight;
-
-    // 3. Find the fractional pixel units and convert to world units
-    const fractX = camPosRightPx - Math.round(camPosRightPx);
-    const fractY = camPosUpPx - Math.round(camPosUpPx);
-
-    // 4. Add fractional world units to the left/right top/bottom to align with the pixel grid
-    camera.left = -aspectRatio - fractX * pixelWidth;
-    camera.right = aspectRatio - fractX * pixelWidth;
-    camera.top = 1.0 - fractY * pixelHeight;
-    camera.bottom = -1.0 - fractY * pixelHeight;
-    camera.updateProjectionMatrix();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_sobel.ts b/examples-testing/examples/webgpu_postprocessing_sobel.ts
deleted file mode 100644
index 01aa16ecd..000000000
--- a/examples-testing/examples/webgpu_postprocessing_sobel.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as THREE from 'three';
-import { pass, sobel } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-let postProcessing;
-
-const params = {
-    enable: true,
-};
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(0, 1, 3);
-    camera.lookAt(scene.position);
-
-    //
-
-    const ambientLight = new THREE.AmbientLight(0xe7e7e7);
-    scene.add(ambientLight);
-
-    const pointLight = new THREE.PointLight(0xffffff, 20);
-    camera.add(pointLight);
-    scene.add(camera);
-
-    //
-
-    const geometry = new THREE.TorusKnotGeometry(1, 0.3, 256, 32);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffff00 });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableZoom = false;
-
-    // postprocessing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePass = pass(scene, camera);
-    const scenePassColor = scenePass.getTextureNode();
-
-    postProcessing.outputNode = sobel(scenePassColor);
-
-    //
-
-    const gui = new GUI();
-
-    gui.add(params, 'enable');
-    gui.open();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    if (params.enable === true) {
-        postProcessing.render();
-    } else {
-        renderer.render(scene, camera);
-    }
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_ssaa.ts b/examples-testing/examples/webgpu_postprocessing_ssaa.ts
deleted file mode 100644
index 76e3a95cd..000000000
--- a/examples-testing/examples/webgpu_postprocessing_ssaa.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import * as THREE from 'three';
-import { ssaaPass } from 'three/tsl';
-
-import { Timer } from 'three/addons/misc/Timer.js';
-import Stats from 'three/addons/libs/stats.module.js';
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let scene, mesh, renderer, postProcessing;
-let camera, ssaaRenderPass;
-let gui, stats, timer;
-
-const params = {
-    sampleLevel: 3,
-    camera: 'perspective',
-    clearColor: 'black',
-    clearAlpha: 1.0,
-    viewOffsetX: 0,
-    autoRotate: true,
-};
-
-init();
-
-clearGui();
-
-function clearGui() {
-    if (gui) gui.destroy();
-
-    gui = new GUI();
-
-    gui.add(params, 'sampleLevel', {
-        'Level 0: 1 Sample': 0,
-        'Level 1: 2 Samples': 1,
-        'Level 2: 4 Samples': 2,
-        'Level 3: 8 Samples': 3,
-        'Level 4: 16 Samples': 4,
-        'Level 5: 32 Samples': 5,
-    });
-    gui.add(params, 'clearColor', ['black', 'white', 'blue', 'green', 'red']);
-    gui.add(params, 'clearAlpha', 0, 1);
-    gui.add(params, 'viewOffsetX', -100, 100);
-    gui.add(params, 'autoRotate');
-
-    gui.open();
-}
-
-function init() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    stats = new Stats();
-    document.body.appendChild(stats.dom);
-
-    timer = new Timer();
-
-    camera = new THREE.PerspectiveCamera(65, width / height, 3, 10);
-    camera.position.z = 7;
-    camera.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
-
-    scene = new THREE.Scene();
-
-    const group = new THREE.Group();
-    scene.add(group);
-
-    const light = new THREE.PointLight(0xefffef, 500);
-    light.position.z = 10;
-    light.position.y = -10;
-    light.position.x = -10;
-    scene.add(light);
-
-    const light2 = new THREE.PointLight(0xffefef, 500);
-    light2.position.z = 10;
-    light2.position.x = -10;
-    light2.position.y = 10;
-    scene.add(light2);
-
-    const light3 = new THREE.PointLight(0xefefff, 500);
-    light3.position.z = 10;
-    light3.position.x = 10;
-    light3.position.y = -10;
-    scene.add(light3);
-
-    const light4 = new THREE.AmbientLight(0xffffff, 0.2);
-    scene.add(light4);
-
-    const geometry = new THREE.SphereGeometry(3, 48, 24);
-    const material = new THREE.MeshStandardMaterial();
-
-    mesh = new THREE.InstancedMesh(geometry, material, 120);
-
-    const dummy = new THREE.Mesh();
-    const color = new THREE.Color();
-
-    for (let i = 0; i < mesh.count; i++) {
-        dummy.position.x = Math.random() * 4 - 2;
-        dummy.position.y = Math.random() * 4 - 2;
-        dummy.position.z = Math.random() * 4 - 2;
-        dummy.rotation.x = Math.random();
-        dummy.rotation.y = Math.random();
-        dummy.rotation.z = Math.random();
-        dummy.scale.setScalar(Math.random() * 0.2 + 0.05);
-
-        dummy.updateMatrix();
-
-        color.setHSL(Math.random(), 1.0, 0.3);
-
-        mesh.setMatrixAt(i, dummy.matrix);
-        mesh.setColorAt(i, color);
-    }
-
-    scene.add(mesh);
-
-    // postprocessing
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    ssaaRenderPass = ssaaPass(scene, camera);
-    const scenePassColor = ssaaRenderPass.getTextureNode();
-
-    postProcessing.outputNode = scenePassColor;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    const width = window.innerWidth;
-    const height = window.innerHeight;
-
-    camera.aspect = width / height;
-    camera.setViewOffset(width, height, params.viewOffsetX, 0, width, height);
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(width, height);
-}
-
-function animate() {
-    timer.update();
-
-    if (params.autoRotate) {
-        const delta = timer.getDelta();
-
-        mesh.rotation.x += delta * 0.25;
-        mesh.rotation.y += delta * 0.5;
-    }
-
-    let newColor = ssaaRenderPass.clearColor;
-
-    switch (params.clearColor) {
-        case 'blue':
-            newColor = 0x0000ff;
-            break;
-        case 'red':
-            newColor = 0xff0000;
-            break;
-        case 'green':
-            newColor = 0x00ff00;
-            break;
-        case 'white':
-            newColor = 0xffffff;
-            break;
-        case 'black':
-            newColor = 0x000000;
-            break;
-    }
-
-    ssaaRenderPass.clearColor.set(newColor);
-    ssaaRenderPass.clearAlpha = params.clearAlpha;
-
-    ssaaRenderPass.sampleLevel = params.sampleLevel;
-
-    camera.view.offsetX = params.viewOffsetX;
-
-    postProcessing.render();
-
-    stats.update();
-}
diff --git a/examples-testing/examples/webgpu_postprocessing_transition.ts b/examples-testing/examples/webgpu_postprocessing_transition.ts
deleted file mode 100644
index b66bad12c..000000000
--- a/examples-testing/examples/webgpu_postprocessing_transition.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import TWEEN from 'three/addons/libs/tween.module.js';
-import { uniform, transition, pass } from 'three/tsl';
-
-let renderer, postProcessing, transitionController, transitionPass;
-
-const textures = [];
-const clock = new THREE.Clock();
-
-const effectController = {
-    animateScene: true,
-    animateTransition: true,
-    transition: 0,
-    _transition: uniform(0),
-    useTexture: true,
-    _useTexture: uniform(1),
-    texture: 5,
-    cycle: true,
-    threshold: uniform(0.1),
-};
-
-function generateInstancedMesh(geometry, material, count) {
-    const mesh = new THREE.InstancedMesh(geometry, material, count);
-
-    const dummy = new THREE.Object3D();
-    const color = new THREE.Color();
-
-    for (let i = 0; i < count; i++) {
-        dummy.position.x = Math.random() * 100 - 50;
-        dummy.position.y = Math.random() * 60 - 30;
-        dummy.position.z = Math.random() * 80 - 40;
-
-        dummy.rotation.x = Math.random() * 2 * Math.PI;
-        dummy.rotation.y = Math.random() * 2 * Math.PI;
-        dummy.rotation.z = Math.random() * 2 * Math.PI;
-
-        dummy.scale.x = Math.random() * 2 + 1;
-
-        if (geometry.type === 'BoxGeometry') {
-            dummy.scale.y = Math.random() * 2 + 1;
-            dummy.scale.z = Math.random() * 2 + 1;
-        } else {
-            dummy.scale.y = dummy.scale.x;
-            dummy.scale.z = dummy.scale.x;
-        }
-
-        dummy.updateMatrix();
-
-        mesh.setMatrixAt(i, dummy.matrix);
-        mesh.setColorAt(i, color.setScalar(0.1 + 0.9 * Math.random()));
-    }
-
-    return mesh;
-}
-
-function FXScene(geometry, rotationSpeed, backgroundColor) {
-    const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.z = 20;
-
-    // Setup scene
-    const scene = new THREE.Scene();
-    scene.background = new THREE.Color(backgroundColor);
-    scene.add(new THREE.AmbientLight(0xaaaaaa, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 1, 4);
-    scene.add(light);
-
-    this.rotationSpeed = rotationSpeed;
-
-    const color = geometry.type === 'BoxGeometry' ? 0x0000ff : 0xff0000;
-    const material = new THREE.MeshPhongNodeMaterial({ color: color, flatShading: true });
-    const mesh = generateInstancedMesh(geometry, material, 500);
-    scene.add(mesh);
-
-    this.scene = scene;
-    this.camera = camera;
-    this.mesh = mesh;
-
-    this.update = function (delta) {
-        if (effectController.animateScene) {
-            mesh.rotation.x += this.rotationSpeed.x * delta;
-            mesh.rotation.y += this.rotationSpeed.y * delta;
-            mesh.rotation.z += this.rotationSpeed.z * delta;
-        }
-    };
-
-    this.resize = function () {
-        camera.aspect = window.innerWidth / window.innerHeight;
-        camera.updateProjectionMatrix();
-    };
-}
-
-const fxSceneA = new FXScene(new THREE.BoxGeometry(2, 2, 2), new THREE.Vector3(0, -0.4, 0), 0xffffff);
-const fxSceneB = new FXScene(new THREE.IcosahedronGeometry(1, 1), new THREE.Vector3(0, 0.2, 0.1), 0x000000);
-
-function init() {
-    // Initialize textures
-
-    const loader = new THREE.TextureLoader();
-
-    for (let i = 0; i < 6; i++) {
-        textures[i] = loader.load('textures/transition/transition' + (i + 1) + '.png');
-    }
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    postProcessing = new THREE.PostProcessing(renderer);
-
-    const scenePassA = pass(fxSceneA.scene, fxSceneA.camera);
-    const scenePassB = pass(fxSceneB.scene, fxSceneB.camera);
-
-    transitionPass = transition(
-        scenePassA,
-        scenePassB,
-        new THREE.TextureNode(textures[effectController.texture]),
-        effectController._transition,
-        effectController.threshold,
-        effectController._useTexture,
-    );
-
-    postProcessing.outputNode = transitionPass;
-
-    const gui = new GUI();
-
-    gui.add(effectController, 'animateScene').name('Animate Scene');
-    gui.add(effectController, 'animateTransition').name('Animate Transition');
-    transitionController = gui
-        .add(effectController, 'transition', 0, 1, 0.01)
-        .name('transition')
-        .onChange(() => {
-            effectController._transition.value = effectController.transition;
-        });
-    gui.add(effectController, 'useTexture').onChange(() => {
-        const value = effectController.useTexture ? 1 : 0;
-        effectController._useTexture.value = value;
-    });
-    gui.add(effectController, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 });
-    gui.add(effectController, 'cycle');
-    gui.add(effectController.threshold, 'value', 0, 1, 0.01).name('threshold');
-}
-
-window.addEventListener('resize', onWindowResize);
-
-function onWindowResize() {
-    fxSceneA.resize();
-    fxSceneB.resize();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-new TWEEN.Tween(effectController)
-    .to({ transition: 1 }, 1500)
-    .onUpdate(function () {
-        transitionController.setValue(effectController.transition);
-
-        // Change the current alpha texture after each transition
-        if (effectController.cycle) {
-            if (effectController.transition == 0 || effectController.transition == 1) {
-                effectController.texture = (effectController.texture + 1) % textures.length;
-            }
-        }
-    })
-    .repeat(Infinity)
-    .delay(2000)
-    .yoyo(true)
-    .start();
-
-function animate() {
-    if (effectController.animateTransition) TWEEN.update();
-
-    if (textures[effectController.texture]) {
-        const mixTexture = textures[effectController.texture];
-        transitionPass.mixTextureNode.value = mixTexture;
-    }
-
-    const delta = clock.getDelta();
-    fxSceneA.update(delta);
-    fxSceneB.update(delta);
-
-    render();
-}
-
-function render() {
-    // Prevent render both scenes when it's not necessary
-    if (effectController.transition === 0) {
-        renderer.render(fxSceneB.scene, fxSceneB.camera);
-    } else if (effectController.transition === 1) {
-        renderer.render(fxSceneA.scene, fxSceneA.camera);
-    } else {
-        postProcessing.render();
-    }
-}
-
-init();
diff --git a/examples-testing/examples/webgpu_procedural_texture.ts b/examples-testing/examples/webgpu_procedural_texture.ts
deleted file mode 100644
index 84e8ba9e4..000000000
--- a/examples-testing/examples/webgpu_procedural_texture.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as THREE from 'three';
-import { checker, uv, uniform, gaussianBlur, convertToTexture } from 'three/tsl';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
-let camera, scene, renderer;
-
-init();
-render();
-
-function init() {
-    const aspect = window.innerWidth / window.innerHeight;
-    camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0, 2);
-    camera.position.z = 1;
-
-    scene = new THREE.Scene();
-
-    // procedural to texture
-
-    const uvScale = uniform(4);
-    const blurAmount = uniform(0.5);
-
-    const procedural = checker(uv().mul(uvScale));
-    const proceduralToTexture = convertToTexture(procedural, 512, 512); // ( node, width, height )
-
-    const colorNode = gaussianBlur(proceduralToTexture, blurAmount, 10);
-
-    // extra
-
-    //proceduralToTexture.autoUpdate = false; // update just once
-    //proceduralToTexture.textureNeedsUpdate = true; // manually update
-
-    // scene
-
-    const material = new THREE.MeshBasicNodeMaterial();
-    material.colorNode = colorNode;
-
-    const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
-    scene.add(plane);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(render);
-    document.body.appendChild(renderer.domElement);
-
-    window.addEventListener('resize', onWindowResize);
-
-    // gui
-
-    const gui = new GUI();
-    gui.add(uvScale, 'value', 1, 10).name('uv scale ( before rtt )');
-    gui.add(blurAmount, 'value', 0, 2).name('blur amount ( after rtt )');
-    gui.add(proceduralToTexture, 'autoUpdate').name('auto update');
-}
-
-function onWindowResize() {
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    const aspect = window.innerWidth / window.innerHeight;
-
-    const frustumHeight = camera.top - camera.bottom;
-
-    camera.left = (-frustumHeight * aspect) / 2;
-    camera.right = (frustumHeight * aspect) / 2;
-
-    camera.updateProjectionMatrix();
-}
-
-function render() {
-    renderer.renderAsync(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_refraction.ts b/examples-testing/examples/webgpu_refraction.ts
deleted file mode 100644
index a410c04f7..000000000
--- a/examples-testing/examples/webgpu_refraction.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as THREE from 'three';
-import { viewportSafeUV, viewportSharedTexture, viewportUV, texture, uv } from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer;
-
-let cameraControls;
-
-let smallSphere;
-
-init();
-
-function init() {
-    // scene
-    scene = new THREE.Scene();
-
-    // camera
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
-    camera.position.set(0, 50, 160);
-
-    //
-
-    const geometry = new THREE.IcosahedronGeometry(5, 0);
-    const material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: 0x7b7b7b, flatShading: true });
-    smallSphere = new THREE.Mesh(geometry, material);
-    scene.add(smallSphere);
-
-    // textures
-
-    const loader = new THREE.TextureLoader();
-
-    const floorNormal = loader.load('textures/floors/FloorsCheckerboard_S_Normal.jpg');
-    floorNormal.wrapS = THREE.RepeatWrapping;
-    floorNormal.wrapT = THREE.RepeatWrapping;
-
-    // refractor
-
-    const verticalNormalScale = 0.1;
-    const verticalUVOffset = texture(floorNormal, uv().mul(5)).xy.mul(2).sub(1).mul(verticalNormalScale);
-
-    const refractorUV = viewportUV.add(verticalUVOffset);
-    const verticalRefractor = viewportSharedTexture(viewportSafeUV(refractorUV));
-
-    const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);
-
-    const planeRefractor = new THREE.Mesh(
-        planeGeo,
-        new THREE.MeshBasicNodeMaterial({
-            backdropNode: verticalRefractor,
-        }),
-    );
-    planeRefractor.material.transparent = true;
-    planeRefractor.position.y = 50;
-    scene.add(planeRefractor);
-
-    // walls
-
-    const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeTop.position.y = 100;
-    planeTop.rotateX(Math.PI / 2);
-    scene.add(planeTop);
-
-    const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }));
-    planeBottom.rotateX(-Math.PI / 2);
-    scene.add(planeBottom);
-
-    const planeBack = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x7f7fff }));
-    planeBack.position.z = -50;
-    planeBack.position.y = 50;
-    scene.add(planeBack);
-
-    const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }));
-    planeRight.position.x = 50;
-    planeRight.position.y = 50;
-    planeRight.rotateY(-Math.PI / 2);
-    scene.add(planeRight);
-
-    const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }));
-    planeLeft.position.x = -50;
-    planeLeft.position.y = 50;
-    planeLeft.rotateY(Math.PI / 2);
-    scene.add(planeLeft);
-
-    // lights
-
-    const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
-    mainLight.position.y = 60;
-    scene.add(mainLight);
-
-    const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
-    greenLight.position.set(550, 50, 0);
-    scene.add(greenLight);
-
-    const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
-    redLight.position.set(-550, 50, 0);
-    scene.add(redLight);
-
-    const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
-    blueLight.position.set(0, 50, 550);
-    scene.add(blueLight);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer(/*{ antialias: true }*/);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    cameraControls = new OrbitControls(camera, renderer.domElement);
-    cameraControls.target.set(0, 50, 0);
-    cameraControls.maxDistance = 400;
-    cameraControls.minDistance = 10;
-    cameraControls.update();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const timer = Date.now() * 0.01;
-
-    smallSphere.position.set(
-        Math.cos(timer * 0.1) * 30,
-        Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
-        Math.sin(timer * 0.1) * 30,
-    );
-    smallSphere.rotation.y = Math.PI / 2 - timer * 0.1;
-    smallSphere.rotation.z = timer * 0.8;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_sky.ts b/examples-testing/examples/webgpu_sky.ts
deleted file mode 100644
index 097d06af6..000000000
--- a/examples-testing/examples/webgpu_sky.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
-
-let camera, scene, renderer;
-
-let sky, sun;
-
-init();
-
-function initSky() {
-    // Add Sky
-    sky = new SkyMesh();
-    sky.scale.setScalar(450000);
-    scene.add(sky);
-
-    sun = new THREE.Vector3();
-
-    /// GUI
-
-    const effectController = {
-        turbidity: 10,
-        rayleigh: 3,
-        mieCoefficient: 0.005,
-        mieDirectionalG: 0.7,
-        elevation: 2,
-        azimuth: 180,
-        exposure: renderer.toneMappingExposure,
-    };
-
-    function guiChanged() {
-        sky.turbidity.value = effectController.turbidity;
-        sky.rayleigh.value = effectController.rayleigh;
-        sky.mieCoefficient.value = effectController.mieCoefficient;
-        sky.mieDirectionalG.value = effectController.mieDirectionalG;
-
-        const phi = THREE.MathUtils.degToRad(90 - effectController.elevation);
-        const theta = THREE.MathUtils.degToRad(effectController.azimuth);
-
-        sun.setFromSphericalCoords(1, phi, theta);
-
-        sky.sunPosition.value.copy(sun);
-
-        renderer.toneMappingExposure = effectController.exposure;
-    }
-
-    const gui = new GUI();
-
-    gui.add(effectController, 'turbidity', 0.0, 20.0, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'rayleigh', 0.0, 4, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'mieCoefficient', 0.0, 0.1, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'mieDirectionalG', 0.0, 1, 0.001).onChange(guiChanged);
-    gui.add(effectController, 'elevation', 0, 90, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'azimuth', -180, 180, 0.1).onChange(guiChanged);
-    gui.add(effectController, 'exposure', 0, 1, 0.0001).onChange(guiChanged);
-
-    guiChanged();
-}
-
-function init() {
-    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 2000000);
-    camera.position.set(0, 100, 2000);
-
-    scene = new THREE.Scene();
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 0.5;
-    document.body.appendChild(renderer.domElement);
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    //controls.maxPolarAngle = Math.PI / 2;
-    controls.enableZoom = false;
-    controls.enablePan = false;
-
-    initSky();
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_texture2darray_compressed.ts b/examples-testing/examples/webgpu_texture2darray_compressed.ts
deleted file mode 100644
index 3e8bf7ee6..000000000
--- a/examples-testing/examples/webgpu_texture2darray_compressed.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as THREE from 'three';
-
-import { texture, uniform, uv } from 'three/tsl';
-
-import Stats from 'three/addons/libs/stats.module.js';
-import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
-
-let camera, scene, mesh, renderer, stats, clock;
-
-const depth = uniform(0);
-
-const planeWidth = 50;
-const planeHeight = 25;
-
-let depthStep = 1;
-
-init();
-
-async function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
-    camera.position.z = 70;
-
-    scene = new THREE.Scene();
-
-    //
-    clock = new THREE.Clock();
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    //
-
-    const ktx2Loader = new KTX2Loader();
-    ktx2Loader.setTranscoderPath('jsm/libs/basis/');
-    await ktx2Loader.detectSupportAsync(renderer);
-
-    ktx2Loader.load('textures/spiritedaway.ktx2', function (texturearray) {
-        const material = new THREE.NodeMaterial();
-
-        material.colorNode = texture(texturearray, uv().flipY()).depth(depth);
-        const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight);
-
-        mesh = new THREE.Mesh(geometry, material);
-
-        scene.add(mesh);
-    });
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    if (mesh) {
-        const delta = clock.getDelta() * 10;
-
-        depthStep += delta;
-
-        const value = depthStep % 5;
-
-        depth.value = value;
-    }
-
-    render();
-    stats.update();
-}
-
-function render() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_textures_anisotropy.ts b/examples-testing/examples/webgpu_textures_anisotropy.ts
deleted file mode 100644
index 7eb0ce1b3..000000000
--- a/examples-testing/examples/webgpu_textures_anisotropy.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import * as THREE from 'three';
-
-import Stats from 'three/addons/libs/stats.module.js';
-
-let container, stats;
-
-let camera, scene1, scene2, renderer;
-
-let mouseX = 0,
-    mouseY = 0;
-
-init();
-
-function init() {
-    const SCREEN_WIDTH = window.innerWidth;
-    const SCREEN_HEIGHT = window.innerHeight;
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
-
-    // RENDERER
-
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
-    renderer.setAnimationLoop(animate);
-    renderer.autoClear = false;
-
-    renderer.domElement.style.position = 'relative';
-    container.appendChild(renderer.domElement);
-
-    //
-
-    camera = new THREE.PerspectiveCamera(35, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 25000);
-    camera.position.z = 1500;
-
-    scene1 = new THREE.Scene();
-    scene1.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
-
-    scene2 = new THREE.Scene();
-    scene2.fog = new THREE.Fog(0xf2f7ff, 1, 25000);
-
-    scene1.add(new THREE.AmbientLight(0xeef0ff, 3));
-    scene2.add(new THREE.AmbientLight(0xeef0ff, 3));
-
-    const light1 = new THREE.DirectionalLight(0xffffff, 6);
-    light1.position.set(1, 1, 1);
-    scene1.add(light1);
-
-    const light2 = new THREE.DirectionalLight(0xffffff, 6);
-    light2.position.set(1, 1, 1);
-    scene2.add(light2);
-
-    // GROUND
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const maxAnisotropy = renderer.getMaxAnisotropy();
-
-    const texture1 = textureLoader.load('textures/crate.gif');
-    const material1 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture1 });
-
-    texture1.colorSpace = THREE.SRGBColorSpace;
-    texture1.anisotropy = renderer.getMaxAnisotropy();
-    texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
-    texture1.repeat.set(512, 512);
-
-    const texture2 = textureLoader.load('textures/crate.gif');
-    const material2 = new THREE.MeshPhongMaterial({ color: 0xffffff, map: texture2 });
-
-    texture2.colorSpace = THREE.SRGBColorSpace;
-    texture2.anisotropy = 1;
-    texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping;
-    texture2.repeat.set(512, 512);
-
-    if (maxAnisotropy > 0) {
-        document.getElementById('val_left').innerHTML = texture1.anisotropy;
-        document.getElementById('val_right').innerHTML = texture2.anisotropy;
-    } else {
-        document.getElementById('val_left').innerHTML = 'not supported';
-        document.getElementById('val_right').innerHTML = 'not supported';
-    }
-
-    //
-
-    const geometry = new THREE.PlaneGeometry(100, 100);
-
-    const mesh1 = new THREE.Mesh(geometry, material1);
-    mesh1.rotation.x = -Math.PI / 2;
-    mesh1.scale.set(1000, 1000, 1000);
-
-    const mesh2 = new THREE.Mesh(geometry, material2);
-    mesh2.rotation.x = -Math.PI / 2;
-    mesh2.scale.set(1000, 1000, 1000);
-
-    scene1.add(mesh1);
-    scene2.add(mesh2);
-
-    // STATS1
-
-    stats = new Stats();
-    container.appendChild(stats.dom);
-
-    document.addEventListener('mousemove', onDocumentMouseMove);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onDocumentMouseMove(event) {
-    const windowHalfX = window.innerWidth / 2;
-    const windowHalfY = window.innerHeight / 2;
-
-    mouseX = event.clientX - windowHalfX;
-    mouseY = event.clientY - windowHalfY;
-}
-
-function animate() {
-    render();
-    stats.update();
-}
-
-function render() {
-    const SCREEN_WIDTH = window.innerWidth;
-    const SCREEN_HEIGHT = window.innerHeight;
-
-    camera.position.x += (mouseX - camera.position.x) * 0.05;
-    camera.position.y = THREE.MathUtils.clamp(
-        camera.position.y + (-(mouseY - 200) - camera.position.y) * 0.05,
-        50,
-        1000,
-    );
-
-    camera.lookAt(scene1.position);
-    renderer.clear();
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(0, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene1, camera);
-
-    renderer.setScissorTest(true);
-
-    renderer.setScissor(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2 - 2, SCREEN_HEIGHT);
-    renderer.render(scene2, camera);
-
-    renderer.setScissorTest(false);
-}
diff --git a/examples-testing/examples/webgpu_textures_partialupdate.ts b/examples-testing/examples/webgpu_textures_partialupdate.ts
deleted file mode 100644
index e8ebe87db..000000000
--- a/examples-testing/examples/webgpu_textures_partialupdate.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer, clock, dataTexture, diffuseMap;
-
-let last = 0;
-const position = new THREE.Vector2();
-const color = new THREE.Color();
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
-    camera.position.z = 2;
-
-    scene = new THREE.Scene();
-
-    clock = new THREE.Clock();
-
-    const loader = new THREE.TextureLoader();
-    diffuseMap = loader.load('textures/carbon/Carbon.png', animate);
-    diffuseMap.colorSpace = THREE.SRGBColorSpace;
-    diffuseMap.minFilter = THREE.LinearFilter;
-    diffuseMap.generateMipmaps = false;
-
-    const geometry = new THREE.PlaneGeometry(2, 2);
-    const material = new THREE.MeshBasicMaterial({ map: diffuseMap });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    //
-
-    const width = 32;
-    const height = 32;
-
-    const data = new Uint8Array(width * height * 4);
-    dataTexture = new THREE.DataTexture(data, width, height);
-
-    //
-
-    renderer = new THREE.WebGPURenderer({ antialias: true, forceWebGL: false });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-
-    document.body.appendChild(renderer.domElement);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-async function animate() {
-    requestAnimationFrame(animate);
-
-    const elapsedTime = clock.getElapsedTime();
-
-    await renderer.renderAsync(scene, camera);
-
-    if (elapsedTime - last > 0.1) {
-        last = elapsedTime;
-
-        position.x = 32 * THREE.MathUtils.randInt(1, 16) - 32;
-        position.y = 32 * THREE.MathUtils.randInt(1, 16) - 32;
-
-        // generate new color data
-        updateDataTexture(dataTexture);
-
-        // perform copy from src to dest texture to a random position
-
-        renderer.copyTextureToTexture(dataTexture, diffuseMap, null, position);
-    }
-}
-
-function updateDataTexture(texture) {
-    const size = texture.image.width * texture.image.height;
-    const data = texture.image.data;
-
-    // generate a random color and update texture data
-
-    color.setHex(Math.random() * 0xffffff);
-
-    const r = Math.floor(color.r * 255);
-    const g = Math.floor(color.g * 255);
-    const b = Math.floor(color.b * 255);
-
-    for (let i = 0; i < size; i++) {
-        const stride = i * 4;
-
-        data[stride] = r;
-        data[stride + 1] = g;
-        data[stride + 2] = b;
-        data[stride + 3] = 1;
-    }
-
-    texture.needsUpdate = true;
-}
diff --git a/examples-testing/examples/webgpu_tsl_coffee_smoke.ts b/examples-testing/examples/webgpu_tsl_coffee_smoke.ts
deleted file mode 100644
index 5c2c8ea48..000000000
--- a/examples-testing/examples/webgpu_tsl_coffee_smoke.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import * as THREE from 'three';
-import {
-    mix,
-    mul,
-    positionLocal,
-    smoothstep,
-    texture,
-    timerLocal,
-    rotateUV,
-    Fn,
-    uv,
-    vec2,
-    vec3,
-    vec4,
-} from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-
-let camera, scene, renderer, controls;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(8, 10, 12);
-
-    scene = new THREE.Scene();
-
-    // Loaders
-
-    const gltfLoader = new GLTFLoader();
-    const textureLoader = new THREE.TextureLoader();
-
-    // baked model
-
-    gltfLoader.load('./models/gltf/coffeeMug.glb', gltf => {
-        gltf.scene.getObjectByName('baked').material.map.anisotropy = 8;
-        scene.add(gltf.scene);
-    });
-
-    // geometry
-
-    const smokeGeometry = new THREE.PlaneGeometry(1, 1, 16, 64);
-    smokeGeometry.translate(0, 0.5, 0);
-    smokeGeometry.scale(1.5, 6, 1.5);
-
-    // texture
-
-    const noiseTexture = textureLoader.load('./textures/noises/perlin/128x128.png');
-    noiseTexture.wrapS = THREE.RepeatWrapping;
-    noiseTexture.wrapT = THREE.RepeatWrapping;
-
-    // material
-
-    const smokeMaterial = new THREE.MeshBasicNodeMaterial({
-        transparent: true,
-        side: THREE.DoubleSide,
-        depthWrite: false,
-    });
-    const time = timerLocal();
-
-    // position
-
-    smokeMaterial.positionNode = Fn(() => {
-        // twist
-
-        const twistNoiseUv = vec2(0.5, uv().y.mul(0.2).sub(time.mul(0.005)).mod(1));
-        const twist = texture(noiseTexture, twistNoiseUv).r.mul(10);
-        positionLocal.xz.assign(rotateUV(positionLocal.xz, twist, vec2(0)));
-
-        // wind
-
-        const windOffset = vec2(
-            texture(noiseTexture, vec2(0.25, time.mul(0.01)).mod(1)).r.sub(0.5),
-            texture(noiseTexture, vec2(0.75, time.mul(0.01)).mod(1)).r.sub(0.5),
-        ).mul(uv().y.pow(2).mul(10));
-        positionLocal.addAssign(windOffset);
-
-        return positionLocal;
-    })();
-
-    // color
-
-    smokeMaterial.colorNode = Fn(() => {
-        // alpha
-
-        const alphaNoiseUv = uv()
-            .mul(vec2(0.5, 0.3))
-            .add(vec2(0, time.mul(0.03).negate()));
-        const alpha = mul(
-            // pattern
-            texture(noiseTexture, alphaNoiseUv).r.smoothstep(0.4, 1),
-
-            // edges fade
-            smoothstep(0, 0.1, uv().x),
-            smoothstep(1, 0.9, uv().x),
-            smoothstep(0, 0.1, uv().y),
-            smoothstep(1, 0.9, uv().y),
-        );
-
-        // color
-
-        const finalColor = mix(vec3(0.6, 0.3, 0.2), vec3(1, 1, 1), alpha.pow(3));
-
-        return vec4(finalColor, alpha);
-    })();
-
-    // mesh
-
-    const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
-    smoke.position.y = 1.83;
-    scene.add(smoke);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // controls
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 0.1;
-    controls.maxDistance = 50;
-    controls.target.y = 3;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-async function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_tsl_vfx_flames.ts b/examples-testing/examples/webgpu_tsl_vfx_flames.ts
deleted file mode 100644
index a9110fa8c..000000000
--- a/examples-testing/examples/webgpu_tsl_vfx_flames.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-import * as THREE from 'three';
-import {
-    PI2,
-    spherizeUV,
-    sin,
-    step,
-    texture,
-    timerLocal,
-    Fn,
-    uv,
-    vec2,
-    vec3,
-    vec4,
-    mix,
-    billboarding,
-} from 'three/tsl';
-
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-let camera, scene, renderer, controls;
-
-init();
-
-function init() {
-    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100);
-    camera.position.set(1, 1, 3);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x201919);
-
-    // textures
-
-    const textureLoader = new THREE.TextureLoader();
-
-    const cellularTexture = textureLoader.load('./textures/noises/voronoi/grayscale-256x256.png');
-    const perlinTexture = textureLoader.load('./textures/noises/perlin/rgb-256x256.png');
-
-    // gradient canvas
-
-    const gradient = {};
-    gradient.element = document.createElement('canvas');
-    gradient.element.width = 128;
-    gradient.element.height = 1;
-    gradient.context = gradient.element.getContext('2d');
-
-    gradient.colors = ['#090033', '#5f1f93', '#e02e96', '#ffbd80', '#fff0db'];
-
-    gradient.texture = new THREE.CanvasTexture(gradient.element);
-    gradient.texture.colorSpace = THREE.SRGBColorSpace;
-
-    gradient.update = () => {
-        const fillGradient = gradient.context.createLinearGradient(0, 0, gradient.element.width, 0);
-
-        for (let i = 0; i < gradient.colors.length; i++) {
-            const progress = i / (gradient.colors.length - 1);
-            const color = gradient.colors[i];
-            fillGradient.addColorStop(progress, color);
-        }
-
-        gradient.context.fillStyle = fillGradient;
-        gradient.context.fillRect(0, 0, gradient.element.width, gradient.element.height);
-
-        gradient.texture.needsUpdate = true;
-    };
-
-    gradient.update();
-
-    // flame 1 material
-
-    const flame1Material = new THREE.SpriteNodeMaterial({ transparent: true, side: THREE.DoubleSide });
-
-    flame1Material.colorNode = Fn(() => {
-        const time = timerLocal();
-
-        // main UV
-        const mainUv = uv().toVar();
-        mainUv.assign(spherizeUV(mainUv, 10).mul(0.6).add(0.2)); // spherize
-        mainUv.assign(mainUv.pow(vec2(1, 2))); // stretch
-        mainUv.assign(mainUv.mul(2, 1).sub(vec2(0.5, 0))); // scale
-
-        // gradients
-        const gradient1 = sin(time.mul(10).sub(mainUv.y.mul(PI2).mul(2))).toVar();
-        const gradient2 = mainUv.y.smoothstep(0, 1).toVar();
-        mainUv.x.addAssign(gradient1.mul(gradient2).mul(0.2));
-
-        // cellular noise
-        const cellularUv = mainUv
-            .mul(0.5)
-            .add(vec2(0, time.negate().mul(0.5)))
-            .mod(1);
-        const cellularNoise = texture(cellularTexture, cellularUv, 0).r.oneMinus().smoothstep(0, 0.5).oneMinus();
-        cellularNoise.mulAssign(gradient2);
-
-        // shape
-        const shape = mainUv.sub(0.5).mul(vec2(3, 2)).length().oneMinus().toVar();
-        shape.assign(shape.sub(cellularNoise));
-
-        // gradient color
-        const gradientColor = texture(gradient.texture, vec2(shape.remap(0, 1, 0, 1), 0));
-
-        // output
-        const color = mix(gradientColor, vec3(1), shape.step(0.8).oneMinus());
-        const alpha = shape.smoothstep(0, 0.3);
-        return vec4(color.rgb, alpha);
-    })();
-
-    // flame 2 material
-
-    const flame2Material = new THREE.SpriteNodeMaterial({ transparent: true, side: THREE.DoubleSide });
-
-    flame2Material.colorNode = Fn(() => {
-        const time = timerLocal();
-
-        // main UV
-        const mainUv = uv().toVar();
-        mainUv.assign(spherizeUV(mainUv, 10).mul(0.6).add(0.2)); // spherize
-        mainUv.assign(mainUv.pow(vec2(1, 3))); // stretch
-        mainUv.assign(mainUv.mul(2, 1).sub(vec2(0.5, 0))); // scale
-
-        // perlin noise
-        const perlinUv = mainUv.add(vec2(0, time.negate().mul(1))).mod(1);
-        const perlinNoise = texture(perlinTexture, perlinUv, 0).sub(0.5).mul(1);
-        mainUv.x.addAssign(perlinNoise.x.mul(0.5));
-
-        // gradients
-        const gradient1 = sin(time.mul(10).sub(mainUv.y.mul(PI2).mul(2)));
-        const gradient2 = mainUv.y.smoothstep(0, 1);
-        const gradient3 = mainUv.y.smoothstep(1, 0.7);
-        mainUv.x.addAssign(gradient1.mul(gradient2).mul(0.2));
-
-        // displaced perlin noise
-        const displacementPerlinUv = mainUv
-            .mul(0.5)
-            .add(vec2(0, time.negate().mul(0.25)))
-            .mod(1);
-        const displacementPerlinNoise = texture(perlinTexture, displacementPerlinUv, 0).sub(0.5).mul(1);
-        const displacedPerlinUv = mainUv
-            .add(vec2(0, time.negate().mul(0.5)))
-            .add(displacementPerlinNoise)
-            .mod(1);
-        const displacedPerlinNoise = texture(perlinTexture, displacedPerlinUv, 0).sub(0.5).mul(1);
-        mainUv.x.addAssign(displacedPerlinNoise.mul(0.5));
-
-        // cellular noise
-        const cellularUv = mainUv.add(vec2(0, time.negate().mul(1.5))).mod(1);
-        const cellularNoise = texture(cellularTexture, cellularUv, 0).r.oneMinus().smoothstep(0.25, 1);
-
-        // shape
-        const shape = mainUv.sub(0.5).mul(vec2(6, 1)).length().step(0.5);
-        shape.assign(shape.mul(cellularNoise));
-        shape.mulAssign(gradient3);
-        shape.assign(step(0.01, shape));
-
-        // output
-        return vec4(vec3(1), shape);
-    })();
-
-    // billboarding - follow the camera rotation only horizontally
-
-    flame1Material.vertexNode = billboarding();
-    flame2Material.vertexNode = billboarding();
-
-    // meshes
-
-    const flame1 = new THREE.Sprite(flame1Material);
-    flame1.center.set(0.5, 0);
-    flame1.scale.x = 0.5; // optional
-    flame1.position.x = -0.5;
-    scene.add(flame1);
-
-    const flame2 = new THREE.Sprite(flame2Material);
-    flame2.center.set(0.5, 0);
-    flame2.position.x = 0.5;
-    scene.add(flame2);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    controls = new OrbitControls(camera, renderer.domElement);
-    controls.enableDamping = true;
-    controls.minDistance = 0.1;
-    controls.maxDistance = 50;
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-async function animate() {
-    controls.update();
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_video_panorama.ts b/examples-testing/examples/webgpu_video_panorama.ts
deleted file mode 100644
index e409b3c07..000000000
--- a/examples-testing/examples/webgpu_video_panorama.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import * as THREE from 'three';
-
-let camera, scene, renderer;
-
-let isUserInteracting = false,
-    lon = 0,
-    lat = 0,
-    phi = 0,
-    theta = 0,
-    onPointerDownPointerX = 0,
-    onPointerDownPointerY = 0,
-    onPointerDownLon = 0,
-    onPointerDownLat = 0;
-
-const distance = 0.5;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.25, 10);
-
-    scene = new THREE.Scene();
-
-    const geometry = new THREE.SphereGeometry(5, 60, 40);
-    // invert the geometry on the x-axis so that all of the faces point inward
-    geometry.scale(-1, 1, 1);
-
-    const video = document.getElementById('video');
-    video.play();
-
-    const texture = new THREE.VideoTexture(video);
-    texture.colorSpace = THREE.SRGBColorSpace;
-    const material = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh = new THREE.Mesh(geometry, material);
-    scene.add(mesh);
-
-    renderer = new THREE.WebGPURenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    container.appendChild(renderer.domElement);
-
-    document.addEventListener('pointerdown', onPointerDown);
-    document.addEventListener('pointermove', onPointerMove);
-    document.addEventListener('pointerup', onPointerUp);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onPointerDown(event) {
-    isUserInteracting = true;
-
-    onPointerDownPointerX = event.clientX;
-    onPointerDownPointerY = event.clientY;
-
-    onPointerDownLon = lon;
-    onPointerDownLat = lat;
-}
-
-function onPointerMove(event) {
-    if (isUserInteracting === true) {
-        lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;
-        lat = (onPointerDownPointerY - event.clientY) * 0.1 + onPointerDownLat;
-    }
-}
-
-function onPointerUp() {
-    isUserInteracting = false;
-}
-
-function animate() {
-    update();
-}
-
-function update() {
-    lat = Math.max(-85, Math.min(85, lat));
-    phi = THREE.MathUtils.degToRad(90 - lat);
-    theta = THREE.MathUtils.degToRad(lon);
-
-    camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
-    camera.position.y = distance * Math.cos(phi);
-    camera.position.z = distance * Math.sin(phi) * Math.sin(theta);
-
-    camera.lookAt(0, 0, 0);
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webgpu_water.ts b/examples-testing/examples/webgpu_water.ts
deleted file mode 100644
index 76e09f1f8..000000000
--- a/examples-testing/examples/webgpu_water.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import * as THREE from 'three';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { WaterMesh } from 'three/addons/objects/Water2Mesh.js';
-
-let scene, camera, clock, renderer, water;
-
-let torusKnot;
-
-const params = {
-    color: '#ffffff',
-    scale: 4,
-    flowX: 1,
-    flowY: 1,
-};
-
-init();
-
-function init() {
-    // scene
-
-    scene = new THREE.Scene();
-
-    // camera
-
-    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
-    camera.position.set(-15, 7, 15);
-    camera.lookAt(scene.position);
-
-    // clock
-
-    clock = new THREE.Clock();
-
-    // mesh
-
-    const torusKnotGeometry = new THREE.TorusKnotGeometry(3, 1, 256, 32);
-    const torusKnotMaterial = new THREE.MeshNormalMaterial();
-
-    torusKnot = new THREE.Mesh(torusKnotGeometry, torusKnotMaterial);
-    torusKnot.position.y = 4;
-    torusKnot.scale.set(0.5, 0.5, 0.5);
-    scene.add(torusKnot);
-
-    // ground
-
-    const groundGeometry = new THREE.PlaneGeometry(20, 20);
-    const groundMaterial = new THREE.MeshStandardMaterial({ roughness: 0.8, metalness: 0.4 });
-    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
-    ground.rotation.x = Math.PI * -0.5;
-    scene.add(ground);
-
-    const textureLoader = new THREE.TextureLoader();
-    textureLoader.load('textures/hardwood2_diffuse.jpg', function (map) {
-        map.wrapS = THREE.RepeatWrapping;
-        map.wrapT = THREE.RepeatWrapping;
-        map.anisotropy = 16;
-        map.repeat.set(4, 4);
-        map.colorSpace = THREE.SRGBColorSpace;
-        groundMaterial.map = map;
-        groundMaterial.needsUpdate = true;
-    });
-
-    //
-
-    const normalMap0 = textureLoader.load('textures/water/Water_1_M_Normal.jpg');
-    const normalMap1 = textureLoader.load('textures/water/Water_2_M_Normal.jpg');
-
-    normalMap0.wrapS = normalMap0.wrapT = THREE.RepeatWrapping;
-    normalMap1.wrapS = normalMap1.wrapT = THREE.RepeatWrapping;
-
-    // water
-
-    const waterGeometry = new THREE.PlaneGeometry(20, 20);
-
-    water = new WaterMesh(waterGeometry, {
-        color: params.color,
-        scale: params.scale,
-        flowDirection: new THREE.Vector2(params.flowX, params.flowY),
-        normalMap0: normalMap0,
-        normalMap1: normalMap1,
-    });
-
-    water.position.y = 1;
-    water.rotation.x = Math.PI * -0.5;
-    scene.add(water);
-
-    // skybox
-
-    const cubeTextureLoader = new THREE.CubeTextureLoader();
-    cubeTextureLoader.setPath('textures/cube/Park2/');
-
-    const cubeTexture = cubeTextureLoader.load([
-        'posx.jpg',
-        'negx.jpg',
-        'posy.jpg',
-        'negy.jpg',
-        'posz.jpg',
-        'negz.jpg',
-    ]);
-
-    scene.background = cubeTexture;
-
-    // light
-
-    const ambientLight = new THREE.AmbientLight(0xe7e7e7, 1.2);
-    scene.add(ambientLight);
-
-    const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
-    directionalLight.position.set(-1, 1, 1);
-    scene.add(directionalLight);
-
-    // renderer
-
-    renderer = new THREE.WebGPURenderer();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setAnimationLoop(animate);
-    document.body.appendChild(renderer.domElement);
-
-    // gui
-
-    const gui = new GUI();
-    const waterNode = water.material.fragmentNode;
-
-    gui.addColor(params, 'color').onChange(function (value) {
-        waterNode.color.value.set(value);
-    });
-    gui.add(params, 'scale', 1, 10).onChange(function (value) {
-        waterNode.scale.value = value;
-    });
-    gui.add(params, 'flowX', -1, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            waterNode.flowDirection.value.x = value;
-            waterNode.flowDirection.value.normalize();
-        });
-    gui.add(params, 'flowY', -1, 1)
-        .step(0.01)
-        .onChange(function (value) {
-            waterNode.flowDirection.value.y = value;
-            waterNode.flowDirection.value.normalize();
-        });
-
-    gui.open();
-
-    //
-
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.minDistance = 5;
-    controls.maxDistance = 50;
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const delta = clock.getDelta();
-
-    torusKnot.rotation.x += delta;
-    torusKnot.rotation.y += delta * 0.5;
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_ar_cones.ts b/examples-testing/examples/webxr_ar_cones.ts
deleted file mode 100644
index 95eb34393..000000000
--- a/examples-testing/examples/webxr_ar_cones.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as THREE from 'three';
-import { ARButton } from 'three/addons/webxr/ARButton.js';
-
-let camera, scene, renderer;
-let controller;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
-
-    const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
-    light.position.set(0.5, 1, 0.25);
-    scene.add(light);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    document.body.appendChild(ARButton.createButton(renderer));
-
-    //
-
-    const geometry = new THREE.CylinderGeometry(0, 0.05, 0.2, 32).rotateX(Math.PI / 2);
-
-    function onSelect() {
-        const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random() });
-        const mesh = new THREE.Mesh(geometry, material);
-        mesh.position.set(0, 0, -0.3).applyMatrix4(controller.matrixWorld);
-        mesh.quaternion.setFromRotationMatrix(controller.matrixWorld);
-        scene.add(mesh);
-    }
-
-    controller = renderer.xr.getController(0);
-    controller.addEventListener('select', onSelect);
-    scene.add(controller);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_ar_hittest.ts b/examples-testing/examples/webxr_ar_hittest.ts
deleted file mode 100644
index 1867cc470..000000000
--- a/examples-testing/examples/webxr_ar_hittest.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import * as THREE from 'three';
-import { ARButton } from 'three/addons/webxr/ARButton.js';
-
-let container;
-let camera, scene, renderer;
-let controller;
-
-let reticle;
-
-let hitTestSource = null;
-let hitTestSourceRequested = false;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
-
-    const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
-    light.position.set(0.5, 1, 0.25);
-    scene.add(light);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    //
-
-    document.body.appendChild(ARButton.createButton(renderer, { requiredFeatures: ['hit-test'] }));
-
-    //
-
-    const geometry = new THREE.CylinderGeometry(0.1, 0.1, 0.2, 32).translate(0, 0.1, 0);
-
-    function onSelect() {
-        if (reticle.visible) {
-            const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random() });
-            const mesh = new THREE.Mesh(geometry, material);
-            reticle.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
-            mesh.scale.y = Math.random() * 2 + 1;
-            scene.add(mesh);
-        }
-    }
-
-    controller = renderer.xr.getController(0);
-    controller.addEventListener('select', onSelect);
-    scene.add(controller);
-
-    reticle = new THREE.Mesh(
-        new THREE.RingGeometry(0.15, 0.2, 32).rotateX(-Math.PI / 2),
-        new THREE.MeshBasicMaterial(),
-    );
-    reticle.matrixAutoUpdate = false;
-    reticle.visible = false;
-    scene.add(reticle);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate(timestamp, frame) {
-    if (frame) {
-        const referenceSpace = renderer.xr.getReferenceSpace();
-        const session = renderer.xr.getSession();
-
-        if (hitTestSourceRequested === false) {
-            session.requestReferenceSpace('viewer').then(function (referenceSpace) {
-                session.requestHitTestSource({ space: referenceSpace }).then(function (source) {
-                    hitTestSource = source;
-                });
-            });
-
-            session.addEventListener('end', function () {
-                hitTestSourceRequested = false;
-                hitTestSource = null;
-            });
-
-            hitTestSourceRequested = true;
-        }
-
-        if (hitTestSource) {
-            const hitTestResults = frame.getHitTestResults(hitTestSource);
-
-            if (hitTestResults.length) {
-                const hit = hitTestResults[0];
-
-                reticle.visible = true;
-                reticle.matrix.fromArray(hit.getPose(referenceSpace).transform.matrix);
-            } else {
-                reticle.visible = false;
-            }
-        }
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_ar_lighting.ts b/examples-testing/examples/webxr_ar_lighting.ts
deleted file mode 100644
index 9de23ad94..000000000
--- a/examples-testing/examples/webxr_ar_lighting.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import * as THREE from 'three';
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { ARButton } from 'three/addons/webxr/ARButton.js';
-import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js';
-
-let camera, scene, renderer;
-let controller;
-let defaultEnvironment;
-
-init();
-
-function init() {
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
-
-    const defaultLight = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
-    defaultLight.position.set(0.5, 1, 0.25);
-    scene.add(defaultLight);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    // Don't add the XREstimatedLight to the scene initially.
-    // It doesn't have any estimated lighting values until an AR session starts.
-
-    const xrLight = new XREstimatedLight(renderer);
-
-    xrLight.addEventListener('estimationstart', () => {
-        // Swap the default light out for the estimated one one we start getting some estimated values.
-        scene.add(xrLight);
-        scene.remove(defaultLight);
-
-        // The estimated lighting also provides an environment cubemap, which we can apply here.
-        if (xrLight.environment) {
-            scene.environment = xrLight.environment;
-        }
-    });
-
-    xrLight.addEventListener('estimationend', () => {
-        // Swap the lights back when we stop receiving estimated values.
-        scene.add(defaultLight);
-        scene.remove(xrLight);
-
-        // Revert back to the default environment.
-        scene.environment = defaultEnvironment;
-    });
-
-    //
-
-    new RGBELoader().setPath('textures/equirectangular/').load('royal_esplanade_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        defaultEnvironment = texture;
-
-        scene.environment = defaultEnvironment;
-    });
-
-    //
-
-    // In order for lighting estimation to work, 'light-estimation' must be included as either an optional or required feature.
-    document.body.appendChild(ARButton.createButton(renderer, { optionalFeatures: ['light-estimation'] }));
-
-    //
-
-    const ballGeometry = new THREE.SphereGeometry(0.175, 32, 32);
-    const ballGroup = new THREE.Group();
-    ballGroup.position.z = -2;
-
-    const rows = 3;
-    const cols = 3;
-
-    for (let i = 0; i < rows; i++) {
-        for (let j = 0; j < cols; j++) {
-            const ballMaterial = new THREE.MeshStandardMaterial({
-                color: 0xdddddd,
-                roughness: i / rows,
-                metalness: j / cols,
-            });
-            const ballMesh = new THREE.Mesh(ballGeometry, ballMaterial);
-            ballMesh.position.set((i + 0.5 - rows * 0.5) * 0.4, (j + 0.5 - cols * 0.5) * 0.4, 0);
-            ballGroup.add(ballMesh);
-        }
-    }
-
-    scene.add(ballGroup);
-
-    //
-
-    function onSelect() {
-        ballGroup.position.set(0, 0, -2).applyMatrix4(controller.matrixWorld);
-        ballGroup.quaternion.setFromRotationMatrix(controller.matrixWorld);
-    }
-
-    controller = renderer.xr.getController(0);
-    controller.addEventListener('select', onSelect);
-    scene.add(controller);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_ar_plane_detection.ts b/examples-testing/examples/webxr_ar_plane_detection.ts
deleted file mode 100644
index 841b6b04b..000000000
--- a/examples-testing/examples/webxr_ar_plane_detection.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import * as THREE from 'three';
-import { ARButton } from 'three/addons/webxr/ARButton.js';
-import { XRPlanes } from 'three/addons/webxr/XRPlanes.js';
-
-//
-
-const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.setAnimationLoop(animate);
-renderer.xr.enabled = true;
-document.body.appendChild(renderer.domElement);
-
-document.body.appendChild(
-    ARButton.createButton(renderer, {
-        requiredFeatures: ['plane-detection'],
-    }),
-);
-
-window.addEventListener('resize', onWindowResize);
-
-//
-
-const scene = new THREE.Scene();
-
-const planes = new XRPlanes(renderer);
-scene.add(planes);
-
-const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
-
-const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3);
-light.position.set(0.5, 1, 0.25);
-scene.add(light);
-
-//
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_vr_handinput.ts b/examples-testing/examples/webxr_vr_handinput.ts
deleted file mode 100644
index d746e4582..000000000
--- a/examples-testing/examples/webxr_vr_handinput.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
-import { XRHandModelFactory } from 'three/addons/webxr/XRHandModelFactory.js';
-
-let container;
-let camera, scene, renderer;
-let hand1, hand2;
-let controller1, controller2;
-let controllerGrip1, controllerGrip2;
-
-let controls;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x444444);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
-    camera.position.set(0, 1.6, 3);
-
-    controls = new OrbitControls(camera, container);
-    controls.target.set(0, 1.6, 0);
-    controls.update();
-
-    const floorGeometry = new THREE.PlaneGeometry(4, 4);
-    const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
-    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
-    floor.rotation.x = -Math.PI / 2;
-    floor.receiveShadow = true;
-    scene.add(floor);
-
-    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 6, 0);
-    light.castShadow = true;
-    light.shadow.camera.top = 2;
-    light.shadow.camera.bottom = -2;
-    light.shadow.camera.right = 2;
-    light.shadow.camera.left = -2;
-    light.shadow.mapSize.set(4096, 4096);
-    scene.add(light);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.xr.enabled = true;
-
-    container.appendChild(renderer.domElement);
-
-    const sessionInit = {
-        requiredFeatures: ['hand-tracking'],
-    };
-
-    document.body.appendChild(VRButton.createButton(renderer, sessionInit));
-
-    // controllers
-
-    controller1 = renderer.xr.getController(0);
-    scene.add(controller1);
-
-    controller2 = renderer.xr.getController(1);
-    scene.add(controller2);
-
-    const controllerModelFactory = new XRControllerModelFactory();
-    const handModelFactory = new XRHandModelFactory();
-
-    // Hand 1
-    controllerGrip1 = renderer.xr.getControllerGrip(0);
-    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
-    scene.add(controllerGrip1);
-
-    hand1 = renderer.xr.getHand(0);
-    hand1.add(handModelFactory.createHandModel(hand1));
-
-    scene.add(hand1);
-
-    // Hand 2
-    controllerGrip2 = renderer.xr.getControllerGrip(1);
-    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
-    scene.add(controllerGrip2);
-
-    hand2 = renderer.xr.getHand(1);
-    hand2.add(handModelFactory.createHandModel(hand2));
-    scene.add(hand2);
-
-    //
-
-    const geometry = new THREE.BufferGeometry().setFromPoints([
-        new THREE.Vector3(0, 0, 0),
-        new THREE.Vector3(0, 0, -1),
-    ]);
-
-    const line = new THREE.Line(geometry);
-    line.name = 'line';
-    line.scale.z = 5;
-
-    controller1.add(line.clone());
-    controller2.add(line.clone());
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-//
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_vr_panorama.ts b/examples-testing/examples/webxr_vr_panorama.ts
deleted file mode 100644
index 535e1c937..000000000
--- a/examples-testing/examples/webxr_vr_panorama.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as THREE from 'three';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-
-let camera;
-let renderer;
-let scene;
-
-init();
-
-function init() {
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    renderer.xr.setReferenceSpaceType('local');
-    document.body.appendChild(renderer.domElement);
-
-    document.body.appendChild(VRButton.createButton(renderer));
-
-    //
-
-    scene = new THREE.Scene();
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
-    camera.layers.enable(1);
-
-    const geometry = new THREE.BoxGeometry(100, 100, 100);
-    geometry.scale(1, 1, -1);
-
-    const textures = getTexturesFromAtlasFile('textures/cube/sun_temple_stripe_stereo.jpg', 12);
-
-    const materials = [];
-
-    for (let i = 0; i < 6; i++) {
-        materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
-    }
-
-    const skyBox = new THREE.Mesh(geometry, materials);
-    skyBox.layers.set(1);
-    scene.add(skyBox);
-
-    const materialsR = [];
-
-    for (let i = 6; i < 12; i++) {
-        materialsR.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
-    }
-
-    const skyBoxR = new THREE.Mesh(geometry, materialsR);
-    skyBoxR.layers.set(2);
-    scene.add(skyBoxR);
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
-    const textures = [];
-
-    for (let i = 0; i < tilesNum; i++) {
-        textures[i] = new THREE.Texture();
-    }
-
-    const loader = new THREE.ImageLoader();
-    loader.load(atlasImgUrl, function (imageObj) {
-        let canvas, context;
-        const tileWidth = imageObj.height;
-
-        for (let i = 0; i < textures.length; i++) {
-            canvas = document.createElement('canvas');
-            context = canvas.getContext('2d');
-            canvas.height = tileWidth;
-            canvas.width = tileWidth;
-            context.drawImage(imageObj, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
-            textures[i].colorSpace = THREE.SRGBColorSpace;
-            textures[i].image = canvas;
-            textures[i].needsUpdate = true;
-        }
-    });
-
-    return textures;
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_vr_panorama_depth.ts b/examples-testing/examples/webxr_vr_panorama_depth.ts
deleted file mode 100644
index 66215469d..000000000
--- a/examples-testing/examples/webxr_vr_panorama_depth.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import * as THREE from 'three';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-
-let camera, scene, renderer, sphere, clock;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-
-    clock = new THREE.Clock();
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x101010);
-
-    const light = new THREE.AmbientLight(0xffffff, 3);
-    scene.add(light);
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 2000);
-    scene.add(camera);
-
-    // Create the panoramic sphere geometery
-    const panoSphereGeo = new THREE.SphereGeometry(6, 256, 256);
-
-    // Create the panoramic sphere material
-    const panoSphereMat = new THREE.MeshStandardMaterial({
-        side: THREE.BackSide,
-        displacementScale: -4.0,
-    });
-
-    // Create the panoramic sphere mesh
-    sphere = new THREE.Mesh(panoSphereGeo, panoSphereMat);
-
-    // Load and assign the texture and depth map
-    const manager = new THREE.LoadingManager();
-    const loader = new THREE.TextureLoader(manager);
-
-    loader.load('./textures/kandao3.jpg', function (texture) {
-        texture.colorSpace = THREE.SRGBColorSpace;
-        texture.minFilter = THREE.NearestFilter;
-        texture.generateMipmaps = false;
-        sphere.material.map = texture;
-    });
-
-    loader.load('./textures/kandao3_depthmap.jpg', function (depth) {
-        depth.minFilter = THREE.NearestFilter;
-        depth.generateMipmaps = false;
-        sphere.material.displacementMap = depth;
-    });
-
-    // On load complete add the panoramic sphere to the scene
-    manager.onLoad = function () {
-        scene.add(sphere);
-    };
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    renderer.xr.setReferenceSpaceType('local');
-    container.appendChild(renderer.domElement);
-
-    document.body.appendChild(VRButton.createButton(renderer));
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    // If we are not presenting move the camera a little so the effect is visible
-
-    if (renderer.xr.isPresenting === false) {
-        const time = clock.getElapsedTime();
-
-        sphere.rotation.y += 0.001;
-        sphere.position.x = Math.sin(time) * 0.2;
-        sphere.position.z = Math.cos(time) * 0.2;
-    }
-
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_vr_rollercoaster.ts b/examples-testing/examples/webxr_vr_rollercoaster.ts
deleted file mode 100644
index b8c35a9e3..000000000
--- a/examples-testing/examples/webxr_vr_rollercoaster.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-import * as THREE from 'three';
-
-import {
-    RollerCoasterGeometry,
-    RollerCoasterShadowGeometry,
-    RollerCoasterLiftersGeometry,
-    TreesGeometry,
-    SkyGeometry,
-} from 'three/addons/misc/RollerCoaster.js';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-
-let mesh, material, geometry;
-
-const renderer = new THREE.WebGLRenderer({ antialias: true });
-renderer.setPixelRatio(window.devicePixelRatio);
-renderer.setSize(window.innerWidth, window.innerHeight);
-renderer.setAnimationLoop(animate);
-renderer.xr.enabled = true;
-renderer.xr.setReferenceSpaceType('local');
-document.body.appendChild(renderer.domElement);
-
-document.body.appendChild(VRButton.createButton(renderer));
-
-//
-
-const scene = new THREE.Scene();
-scene.background = new THREE.Color(0xf0f0ff);
-
-const light = new THREE.HemisphereLight(0xfff0f0, 0x60606, 3);
-light.position.set(1, 1, 1);
-scene.add(light);
-
-const train = new THREE.Object3D();
-scene.add(train);
-
-const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 500);
-train.add(camera);
-
-// environment
-
-geometry = new THREE.PlaneGeometry(500, 500, 15, 15);
-geometry.rotateX(-Math.PI / 2);
-
-const positions = geometry.attributes.position.array;
-const vertex = new THREE.Vector3();
-
-for (let i = 0; i < positions.length; i += 3) {
-    vertex.fromArray(positions, i);
-
-    vertex.x += Math.random() * 10 - 5;
-    vertex.z += Math.random() * 10 - 5;
-
-    const distance = vertex.distanceTo(scene.position) / 5 - 25;
-    vertex.y = Math.random() * Math.max(0, distance);
-
-    vertex.toArray(positions, i);
-}
-
-geometry.computeVertexNormals();
-
-material = new THREE.MeshLambertMaterial({
-    color: 0x407000,
-});
-
-mesh = new THREE.Mesh(geometry, material);
-scene.add(mesh);
-
-geometry = new TreesGeometry(mesh);
-material = new THREE.MeshBasicMaterial({
-    side: THREE.DoubleSide,
-    vertexColors: true,
-});
-mesh = new THREE.Mesh(geometry, material);
-scene.add(mesh);
-
-geometry = new SkyGeometry();
-material = new THREE.MeshBasicMaterial({ color: 0xffffff });
-mesh = new THREE.Mesh(geometry, material);
-scene.add(mesh);
-
-//
-
-const PI2 = Math.PI * 2;
-
-const curve = (function () {
-    const vector = new THREE.Vector3();
-    const vector2 = new THREE.Vector3();
-
-    return {
-        getPointAt: function (t) {
-            t = t * PI2;
-
-            const x = Math.sin(t * 3) * Math.cos(t * 4) * 50;
-            const y = Math.sin(t * 10) * 2 + Math.cos(t * 17) * 2 + 5;
-            const z = Math.sin(t) * Math.sin(t * 4) * 50;
-
-            return vector.set(x, y, z).multiplyScalar(2);
-        },
-
-        getTangentAt: function (t) {
-            const delta = 0.0001;
-            const t1 = Math.max(0, t - delta);
-            const t2 = Math.min(1, t + delta);
-
-            return vector2.copy(this.getPointAt(t2)).sub(this.getPointAt(t1)).normalize();
-        },
-    };
-})();
-
-geometry = new RollerCoasterGeometry(curve, 1500);
-material = new THREE.MeshPhongMaterial({
-    vertexColors: true,
-});
-mesh = new THREE.Mesh(geometry, material);
-scene.add(mesh);
-
-geometry = new RollerCoasterLiftersGeometry(curve, 100);
-material = new THREE.MeshPhongMaterial();
-mesh = new THREE.Mesh(geometry, material);
-mesh.position.y = 0.1;
-scene.add(mesh);
-
-geometry = new RollerCoasterShadowGeometry(curve, 500);
-material = new THREE.MeshBasicMaterial({
-    color: 0x305000,
-    depthWrite: false,
-    transparent: true,
-});
-mesh = new THREE.Mesh(geometry, material);
-mesh.position.y = 0.1;
-scene.add(mesh);
-
-const funfairs = [];
-
-//
-
-geometry = new THREE.CylinderGeometry(10, 10, 5, 15);
-material = new THREE.MeshLambertMaterial({
-    color: 0xff8080,
-});
-mesh = new THREE.Mesh(geometry, material);
-mesh.position.set(-80, 10, -70);
-mesh.rotation.x = Math.PI / 2;
-scene.add(mesh);
-
-funfairs.push(mesh);
-
-geometry = new THREE.CylinderGeometry(5, 6, 4, 10);
-material = new THREE.MeshLambertMaterial({
-    color: 0x8080ff,
-});
-mesh = new THREE.Mesh(geometry, material);
-mesh.position.set(50, 2, 30);
-scene.add(mesh);
-
-funfairs.push(mesh);
-
-//
-
-window.addEventListener('resize', onWindowResize);
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-const position = new THREE.Vector3();
-const tangent = new THREE.Vector3();
-
-const lookAt = new THREE.Vector3();
-
-let velocity = 0;
-let progress = 0;
-
-let prevTime = performance.now();
-
-function animate() {
-    const time = performance.now();
-    const delta = time - prevTime;
-
-    for (let i = 0; i < funfairs.length; i++) {
-        funfairs[i].rotation.y = time * 0.0004;
-    }
-
-    //
-
-    progress += velocity;
-    progress = progress % 1;
-
-    position.copy(curve.getPointAt(progress));
-    position.y += 0.3;
-
-    train.position.copy(position);
-
-    tangent.copy(curve.getTangentAt(progress));
-
-    velocity -= tangent.y * 0.0000001 * delta;
-    velocity = Math.max(0.00004, Math.min(0.0002, velocity));
-
-    train.lookAt(lookAt.copy(position).sub(tangent));
-
-    //
-
-    renderer.render(scene, camera);
-
-    prevTime = time;
-}
diff --git a/examples-testing/examples/webxr_vr_sandbox.ts b/examples-testing/examples/webxr_vr_sandbox.ts
deleted file mode 100644
index 9e8e75909..000000000
--- a/examples-testing/examples/webxr_vr_sandbox.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import * as THREE from 'three';
-
-import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
-import { Reflector } from 'three/addons/objects/Reflector.js';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-
-import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js';
-import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js';
-import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
-
-import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-import Stats from 'three/addons/libs/stats.module.js';
-
-let camera, scene, renderer;
-let reflector;
-let stats, statsMesh;
-
-const parameters = {
-    radius: 0.6,
-    tube: 0.2,
-    tubularSegments: 150,
-    radialSegments: 20,
-    p: 2,
-    q: 3,
-    thickness: 0.5,
-};
-
-init();
-
-function init() {
-    scene = new THREE.Scene();
-
-    new RGBELoader().setPath('textures/equirectangular/').load('moonless_golf_1k.hdr', function (texture) {
-        texture.mapping = THREE.EquirectangularReflectionMapping;
-
-        scene.background = texture;
-        scene.environment = texture;
-    });
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
-    camera.position.set(0, 1.6, 1.5);
-
-    //
-
-    const torusGeometry = new THREE.TorusKnotGeometry(...Object.values(parameters));
-    const torusMaterial = new THREE.MeshPhysicalMaterial({
-        transmission: 1.0,
-        roughness: 0,
-        metalness: 0.25,
-        thickness: 0.5,
-        side: THREE.DoubleSide,
-    });
-    const torus = new THREE.Mesh(torusGeometry, torusMaterial);
-    torus.name = 'torus';
-    torus.position.y = 1.5;
-    torus.position.z = -2;
-    scene.add(torus);
-
-    const cylinderGeometry = new THREE.CylinderGeometry(1, 1, 0.1, 50);
-    const cylinderMaterial = new THREE.MeshStandardMaterial();
-    const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
-    cylinder.position.z = -2;
-    scene.add(cylinder);
-
-    //
-
-    reflector = new Reflector(new THREE.PlaneGeometry(2, 2), {
-        textureWidth: window.innerWidth,
-        textureHeight: window.innerHeight,
-    });
-    reflector.position.x = 1;
-    reflector.position.y = 1.5;
-    reflector.position.z = -3;
-    reflector.rotation.y = -Math.PI / 4;
-    scene.add(reflector);
-
-    const frameGeometry = new THREE.BoxGeometry(2.1, 2.1, 0.1);
-    const frameMaterial = new THREE.MeshPhongMaterial();
-    const frame = new THREE.Mesh(frameGeometry, frameMaterial);
-    frame.position.z = -0.07;
-    reflector.add(frame);
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.autoClear = false;
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    renderer.toneMapping = THREE.ACESFilmicToneMapping;
-    renderer.toneMappingExposure = 1;
-    document.body.appendChild(renderer.domElement);
-
-    document.body.appendChild(VRButton.createButton(renderer));
-
-    window.addEventListener('resize', onWindowResize);
-
-    //
-
-    const geometry = new THREE.BufferGeometry();
-    geometry.setFromPoints([new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -5)]);
-
-    const controller1 = renderer.xr.getController(0);
-    controller1.add(new THREE.Line(geometry));
-    scene.add(controller1);
-
-    const controller2 = renderer.xr.getController(1);
-    controller2.add(new THREE.Line(geometry));
-    scene.add(controller2);
-
-    //
-
-    const controllerModelFactory = new XRControllerModelFactory();
-
-    const controllerGrip1 = renderer.xr.getControllerGrip(0);
-    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
-    scene.add(controllerGrip1);
-
-    const controllerGrip2 = renderer.xr.getControllerGrip(1);
-    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
-    scene.add(controllerGrip2);
-
-    // GUI
-
-    function onChange() {
-        torus.geometry.dispose();
-        torus.geometry = new THREE.TorusKnotGeometry(...Object.values(parameters));
-    }
-
-    function onThicknessChange() {
-        torus.material.thickness = parameters.thickness;
-    }
-
-    const gui = new GUI({ width: 300 });
-    gui.add(parameters, 'radius', 0.0, 1.0).onChange(onChange);
-    gui.add(parameters, 'tube', 0.0, 1.0).onChange(onChange);
-    gui.add(parameters, 'tubularSegments', 10, 150, 1).onChange(onChange);
-    gui.add(parameters, 'radialSegments', 2, 20, 1).onChange(onChange);
-    gui.add(parameters, 'p', 1, 10, 1).onChange(onChange);
-    gui.add(parameters, 'q', 0, 10, 1).onChange(onChange);
-    gui.add(parameters, 'thickness', 0, 1).onChange(onThicknessChange);
-    gui.domElement.style.visibility = 'hidden';
-
-    const group = new InteractiveGroup();
-    group.listenToPointerEvents(renderer, camera);
-    group.listenToXRControllerEvents(controller1);
-    group.listenToXRControllerEvents(controller2);
-    scene.add(group);
-
-    const mesh = new HTMLMesh(gui.domElement);
-    mesh.position.x = -0.75;
-    mesh.position.y = 1.5;
-    mesh.position.z = -0.5;
-    mesh.rotation.y = Math.PI / 4;
-    mesh.scale.setScalar(2);
-    group.add(mesh);
-
-    // Add stats.js
-    stats = new Stats();
-    stats.dom.style.width = '80px';
-    stats.dom.style.height = '48px';
-    document.body.appendChild(stats.dom);
-
-    statsMesh = new HTMLMesh(stats.dom);
-    statsMesh.position.x = -0.75;
-    statsMesh.position.y = 2;
-    statsMesh.position.z = -0.6;
-    statsMesh.rotation.y = Math.PI / 4;
-    statsMesh.scale.setScalar(2.5);
-    group.add(statsMesh);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    const time = performance.now() * 0.0002;
-    const torus = scene.getObjectByName('torus');
-    torus.rotation.x = time * 0.4;
-    torus.rotation.y = time;
-
-    renderer.render(scene, camera);
-    stats.update();
-
-    // Canvas elements doesn't trigger DOM updates, so we have to update the texture
-    statsMesh.material.map.update();
-}
diff --git a/examples-testing/examples/webxr_vr_video.ts b/examples-testing/examples/webxr_vr_video.ts
deleted file mode 100644
index 50a990412..000000000
--- a/examples-testing/examples/webxr_vr_video.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as THREE from 'three';
-import { VRButton } from 'three/addons/webxr/VRButton.js';
-
-let camera, scene, renderer;
-
-init();
-
-function init() {
-    const container = document.getElementById('container');
-    container.addEventListener('click', function () {
-        video.play();
-    });
-
-    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 2000);
-    camera.layers.enable(1); // render left view when no stereo available
-
-    // video
-
-    const video = document.getElementById('video');
-    video.play();
-
-    const texture = new THREE.VideoTexture(video);
-    texture.colorSpace = THREE.SRGBColorSpace;
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x101010);
-
-    // left
-
-    const geometry1 = new THREE.SphereGeometry(500, 60, 40);
-    // invert the geometry on the x-axis so that all of the faces point inward
-    geometry1.scale(-1, 1, 1);
-
-    const uvs1 = geometry1.attributes.uv.array;
-
-    for (let i = 0; i < uvs1.length; i += 2) {
-        uvs1[i] *= 0.5;
-    }
-
-    const material1 = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh1 = new THREE.Mesh(geometry1, material1);
-    mesh1.rotation.y = -Math.PI / 2;
-    mesh1.layers.set(1); // display in left eye only
-    scene.add(mesh1);
-
-    // right
-
-    const geometry2 = new THREE.SphereGeometry(500, 60, 40);
-    geometry2.scale(-1, 1, 1);
-
-    const uvs2 = geometry2.attributes.uv.array;
-
-    for (let i = 0; i < uvs2.length; i += 2) {
-        uvs2[i] *= 0.5;
-        uvs2[i] += 0.5;
-    }
-
-    const material2 = new THREE.MeshBasicMaterial({ map: texture });
-
-    const mesh2 = new THREE.Mesh(geometry2, material2);
-    mesh2.rotation.y = -Math.PI / 2;
-    mesh2.layers.set(2); // display in right eye only
-    scene.add(mesh2);
-
-    //
-
-    renderer = new THREE.WebGLRenderer();
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.xr.enabled = true;
-    renderer.xr.setReferenceSpaceType('local');
-    container.appendChild(renderer.domElement);
-
-    document.body.appendChild(VRButton.createButton(renderer));
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_xr_controls_transform.ts b/examples-testing/examples/webxr_xr_controls_transform.ts
deleted file mode 100644
index f3b4796e6..000000000
--- a/examples-testing/examples/webxr_xr_controls_transform.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import * as THREE from 'three';
-import { TransformControls } from 'three/addons/controls/TransformControls.js';
-import { XRButton } from 'three/addons/webxr/XRButton.js';
-import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
-
-let container;
-let camera, scene, renderer;
-let controller1, controller2, line;
-let controllerGrip1, controllerGrip2;
-
-let raycaster;
-
-let controls, group;
-
-init();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x808080);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
-    camera.position.set(0, 1.6, 0);
-
-    const floorGeometry = new THREE.PlaneGeometry(6, 6);
-    const floorMaterial = new THREE.ShadowMaterial({
-        opacity: 0.25,
-        blending: THREE.CustomBlending,
-        transparent: false,
-    });
-    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
-    floor.rotation.x = -Math.PI / 2;
-    floor.receiveShadow = true;
-    scene.add(floor);
-
-    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 6, 0);
-    light.castShadow = true;
-    light.shadow.camera.top = 3;
-    light.shadow.camera.bottom = -3;
-    light.shadow.camera.right = 3;
-    light.shadow.camera.left = -3;
-    light.shadow.mapSize.set(4096, 4096);
-    scene.add(light);
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const geometries = [
-        new THREE.BoxGeometry(0.2, 0.2, 0.2),
-        new THREE.ConeGeometry(0.2, 0.4, 64),
-        new THREE.CylinderGeometry(0.2, 0.2, 0.2, 64),
-        new THREE.IcosahedronGeometry(0.2, 8),
-        new THREE.TorusGeometry(0.2, 0.04, 64, 32),
-    ];
-
-    for (let i = 0; i < 16; i++) {
-        const geometry = geometries[Math.floor(Math.random() * geometries.length)];
-        const material = new THREE.MeshStandardMaterial({
-            color: Math.random() * 0xffffff,
-            roughness: 0.7,
-            metalness: 0.0,
-        });
-
-        const object = new THREE.Mesh(geometry, material);
-
-        object.position.x = Math.random() - 0.5;
-        object.position.y = Math.random() * 2 + 0.5;
-        object.position.z = Math.random() - 2.5;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.setScalar(Math.random() + 0.5);
-
-        object.castShadow = true;
-        object.receiveShadow = true;
-
-        group.add(object);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.setAnimationLoop(animate);
-    renderer.shadowMap.enabled = true;
-    renderer.xr.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    document.body.appendChild(XRButton.createButton(renderer));
-
-    // controllers
-
-    controller1 = renderer.xr.getController(0);
-    controller1.addEventListener('select', onSelect);
-    controller1.addEventListener('selectstart', onControllerEvent);
-    controller1.addEventListener('selectend', onControllerEvent);
-    controller1.addEventListener('move', onControllerEvent);
-    controller1.userData.active = false;
-    scene.add(controller1);
-
-    controller2 = renderer.xr.getController(1);
-    controller2.addEventListener('select', onSelect);
-    controller2.addEventListener('selectstart', onControllerEvent);
-    controller2.addEventListener('selectend', onControllerEvent);
-    controller2.addEventListener('move', onControllerEvent);
-    controller2.userData.active = true;
-    scene.add(controller2);
-
-    const controllerModelFactory = new XRControllerModelFactory();
-
-    controllerGrip1 = renderer.xr.getControllerGrip(0);
-    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
-    scene.add(controllerGrip1);
-
-    controllerGrip2 = renderer.xr.getControllerGrip(1);
-    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
-    scene.add(controllerGrip2);
-
-    //
-
-    const geometry = new THREE.BufferGeometry().setFromPoints([
-        new THREE.Vector3(0, 0, 0),
-        new THREE.Vector3(0, 0, -1),
-    ]);
-
-    line = new THREE.Line(geometry);
-    line.name = 'line';
-    line.scale.z = 5;
-
-    raycaster = new THREE.Raycaster();
-
-    // controls
-
-    controls = new TransformControls(camera, renderer.domElement);
-    controls.attach(group.children[0]);
-    scene.add(controls);
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onSelect(event) {
-    const controller = event.target;
-
-    controller1.userData.active = false;
-    controller2.userData.active = false;
-
-    if (controller === controller1) {
-        controller1.userData.active = true;
-        controller1.add(line);
-    }
-
-    if (controller === controller2) {
-        controller2.userData.active = true;
-        controller2.add(line);
-    }
-
-    raycaster.setFromXRController(controller);
-
-    const intersects = raycaster.intersectObjects(group.children);
-
-    if (intersects.length > 0) {
-        controls.attach(intersects[0].object);
-    }
-}
-
-function onControllerEvent(event) {
-    const controller = event.target;
-
-    if (controller.userData.active === false) return;
-
-    controls.getRaycaster().setFromXRController(controller);
-
-    switch (event.type) {
-        case 'selectstart':
-            controls.pointerDown(null);
-            break;
-
-        case 'selectend':
-            controls.pointerUp(null);
-            break;
-
-        case 'move':
-            controls.pointerHover(null);
-            controls.pointerMove(null);
-            break;
-    }
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-//
-
-function animate() {
-    renderer.render(scene, camera);
-}
diff --git a/examples-testing/examples/webxr_xr_dragging_custom_depth.ts b/examples-testing/examples/webxr_xr_dragging_custom_depth.ts
deleted file mode 100644
index 2cd50ba4c..000000000
--- a/examples-testing/examples/webxr_xr_dragging_custom_depth.ts
+++ /dev/null
@@ -1,395 +0,0 @@
-import * as THREE from 'three';
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-import { XRButton } from 'three/addons/webxr/XRButton.js';
-import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
-
-let container;
-let camera, scene, renderer;
-let controller1, controller2;
-let controllerGrip1, controllerGrip2;
-let isDepthSupplied = false;
-
-let raycaster;
-
-const intersected = [];
-const tempMatrix = new THREE.Matrix4();
-
-let controls, group;
-
-init();
-animate();
-
-function init() {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-
-    scene = new THREE.Scene();
-    scene.background = new THREE.Color(0x808080);
-
-    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
-    camera.position.set(0, 1.6, 3);
-
-    controls = new OrbitControls(camera, container);
-    controls.target.set(0, 1.6, 0);
-    controls.update();
-
-    const floorGeometry = new THREE.PlaneGeometry(6, 6);
-    const floorMaterial = new THREE.ShadowMaterial({
-        opacity: 0.25,
-        blending: THREE.CustomBlending,
-        transparent: false,
-    });
-    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
-    floor.rotation.x = -Math.PI / 2;
-    floor.receiveShadow = true;
-    scene.add(floor);
-
-    scene.add(new THREE.HemisphereLight(0xbcbcbc, 0xa5a5a5, 3));
-
-    const light = new THREE.DirectionalLight(0xffffff, 3);
-    light.position.set(0, 6, 0);
-    light.castShadow = true;
-    light.shadow.camera.top = 3;
-    light.shadow.camera.bottom = -3;
-    light.shadow.camera.right = 3;
-    light.shadow.camera.left = -3;
-    light.shadow.mapSize.set(4096, 4096);
-    scene.add(light);
-
-    group = new THREE.Group();
-    scene.add(group);
-
-    const geometries = [
-        new THREE.BoxGeometry(0.2, 0.2, 0.2),
-        new THREE.ConeGeometry(0.2, 0.2, 64),
-        new THREE.CylinderGeometry(0.2, 0.2, 0.2, 64),
-        new THREE.IcosahedronGeometry(0.2, 8),
-        new THREE.TorusGeometry(0.2, 0.04, 64, 32),
-    ];
-
-    for (let i = 0; i < 50; i++) {
-        const geometry = geometries[Math.floor(Math.random() * geometries.length)];
-        const material = new THREE.ShaderMaterial({
-            vertexShader: /* glsl */ `
-							varying vec3 vNormal;
-							varying vec2 vUv;
-							void main() {
-								vNormal = normalize(normalMatrix * normal);
-								vUv = uv;
-								gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
-							}
-						`,
-
-            fragmentShader: /* glsl */ `
-							uniform vec3 diffuseColor;
-							uniform float roughness;
-							uniform float metalness;
-							uniform float emissive;
-							varying vec3 vNormal;
-							varying vec2 vUv;
-							uniform sampler2DArray depthColor;
-							uniform float depthWidth;
-							uniform float depthHeight;
-							#define saturate( a ) clamp( a, 0.0, 1.0 )
-							float Depth_GetCameraDepthInMeters(const sampler2DArray depthTexture,
-								const vec2 depthUv, int arrayIndex) {
-								return texture(depthColor, vec3(depthUv.x, depthUv.y, arrayIndex)).r;
-							}
-							float Depth_GetOcclusion(const sampler2DArray depthTexture, const vec2 depthUv, float assetDepthM, int arrayIndex) {
-								float depthMm = Depth_GetCameraDepthInMeters(depthTexture, depthUv, arrayIndex);
-								const float kDepthTolerancePerM = 0.001;
-								return clamp(1.0 -
-									0.5 * (depthMm - assetDepthM) /
-										(kDepthTolerancePerM * assetDepthM) +
-									0.5, 0.0, 1.0);
-							}
-							float Depth_GetBlurredOcclusionAroundUV(const sampler2DArray depthTexture, const vec2 uv, float assetDepthM, int arrayIndex) {
-								// Kernel used:
-								// 0   4   7   4   0
-								// 4   16  26  16  4
-								// 7   26  41  26  7
-								// 4   16  26  16  4
-								// 0   4   7   4   0
-								const float kKernelTotalWeights = 269.0;
-								float sum = 0.0;
-								const float kOcclusionBlurAmount = 0.0005;
-								vec2 blurriness =
-								vec2(kOcclusionBlurAmount, kOcclusionBlurAmount /** u_DepthAspectRatio*/);
-								float current = 0.0;
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-1.0, -2.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+1.0, -2.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-1.0, +2.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+1.0, +2.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-2.0, +1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+2.0, +1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-2.0, -1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+2.0, -1.0) * blurriness, assetDepthM, arrayIndex);
-								sum += current * 4.0;
-								current = 0.0;
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-2.0, -0.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+2.0, +0.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+0.0, +2.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-0.0, -2.0) * blurriness, assetDepthM, arrayIndex);
-								sum += current * 7.0;
-								current = 0.0;
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-1.0, -1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+1.0, -1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-1.0, +1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+1.0, +1.0) * blurriness, assetDepthM, arrayIndex);
-								sum += current * 16.0;
-								current = 0.0;
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+0.0, +1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-0.0, -1.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(-1.0, -0.0) * blurriness, assetDepthM, arrayIndex);
-								current += Depth_GetOcclusion(
-								depthTexture, uv + vec2(+1.0, +0.0) * blurriness, assetDepthM, arrayIndex);
-								sum += current * 26.0;
-								sum += Depth_GetOcclusion(depthTexture, uv, assetDepthM, arrayIndex) * 41.0;
-								return sum / kKernelTotalWeights;
-							}
-							void main() {
-								vec3 normal = normalize(vNormal);
-								vec3 diffuse = diffuseColor;
-								float specularIntensity = pow(max(dot(normal, normalize(vec3(0, 0, 1))), 0.0), 64.0);
-								vec3 specular = vec3(specularIntensity) * mix(vec3(0.04), diffuse, metalness);
-								gl_FragColor = vec4(diffuse * (1.0 - specular) + specular, 1.0) * (1.0 + emissive);
-
-								if (depthWidth > 0.0) {
-									int arrayIndex = 0;
-									vec2 depthUv;
-									if (gl_FragCoord.x>=depthWidth) {
-										arrayIndex = 1;
-										depthUv = vec2((gl_FragCoord.x-depthWidth)/depthWidth, gl_FragCoord.y/depthHeight);
-									} else {
-										depthUv = vec2(gl_FragCoord.x/depthWidth, gl_FragCoord.y/depthHeight);
-									}
-									float assetDepthM = gl_FragCoord.z;
-
-									float occlusion = Depth_GetBlurredOcclusionAroundUV(depthColor, depthUv, assetDepthM, arrayIndex);
-									float depthMm = Depth_GetCameraDepthInMeters(depthColor, depthUv, arrayIndex);
-
-									float absDistance = abs(assetDepthM - depthMm);
-									float v = 0.0025;
-									absDistance = saturate(v - absDistance) / v;
-
-									gl_FragColor.rgb += vec3(absDistance * 2.0, absDistance * 2.0, absDistance * 12.0);
-									gl_FragColor = mix(gl_FragColor, vec4(0.0, 0.0, 0.0, 0.0), occlusion * 0.7);
-								}
-							}
-							`,
-
-            uniforms: {
-                diffuseColor: { value: new THREE.Color(Math.random() * 0xffffff) },
-                roughness: { value: 0.7 },
-                metalness: { value: 0.0 },
-                emissive: { value: 0.0 },
-                depthWidth: { value: 0.0 },
-                depthHeight: { value: 0.0 },
-                depthColor: { value: new THREE.Texture() },
-            },
-        });
-
-        const object = new THREE.Mesh(geometry, material);
-
-        object.position.x = Math.random() * 4 - 2;
-        object.position.y = Math.random() * 2;
-        object.position.z = Math.random() * 4 - 2;
-
-        object.rotation.x = Math.random() * 2 * Math.PI;
-        object.rotation.y = Math.random() * 2 * Math.PI;
-        object.rotation.z = Math.random() * 2 * Math.PI;
-
-        object.scale.setScalar(Math.random() + 0.5);
-
-        group.add(object);
-    }
-
-    //
-
-    renderer = new THREE.WebGLRenderer({ antialias: true });
-    renderer.setPixelRatio(window.devicePixelRatio);
-    renderer.setSize(window.innerWidth, window.innerHeight);
-    renderer.shadowMap.enabled = true;
-    renderer.xr.enabled = true;
-    container.appendChild(renderer.domElement);
-
-    document.body.appendChild(
-        XRButton.createButton(renderer, {
-            optionalFeatures: ['depth-sensing'],
-            depthSensing: { usagePreference: ['gpu-optimized'], dataFormatPreference: [] },
-        }),
-    );
-
-    // controllers
-
-    controller1 = renderer.xr.getController(0);
-    controller1.addEventListener('selectstart', onSelectStart);
-    controller1.addEventListener('selectend', onSelectEnd);
-    scene.add(controller1);
-
-    controller2 = renderer.xr.getController(1);
-    controller2.addEventListener('selectstart', onSelectStart);
-    controller2.addEventListener('selectend', onSelectEnd);
-    scene.add(controller2);
-
-    const controllerModelFactory = new XRControllerModelFactory();
-
-    controllerGrip1 = renderer.xr.getControllerGrip(0);
-    controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
-    scene.add(controllerGrip1);
-
-    controllerGrip2 = renderer.xr.getControllerGrip(1);
-    controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
-    scene.add(controllerGrip2);
-
-    //
-
-    const geometry = new THREE.BufferGeometry().setFromPoints([
-        new THREE.Vector3(0, 0, 0),
-        new THREE.Vector3(0, 0, -1),
-    ]);
-
-    const line = new THREE.Line(geometry);
-    line.name = 'line';
-    line.scale.z = 5;
-
-    controller1.add(line.clone());
-    controller2.add(line.clone());
-
-    raycaster = new THREE.Raycaster();
-
-    //
-
-    window.addEventListener('resize', onWindowResize);
-}
-
-function onWindowResize() {
-    camera.aspect = window.innerWidth / window.innerHeight;
-    camera.updateProjectionMatrix();
-
-    renderer.setSize(window.innerWidth, window.innerHeight);
-}
-
-function onSelectStart(event) {
-    const controller = event.target;
-
-    const intersections = getIntersections(controller);
-
-    if (intersections.length > 0) {
-        const intersection = intersections[0];
-
-        const object = intersection.object;
-        object.material.uniforms.emissive.value = 1;
-        controller.attach(object);
-
-        controller.userData.selected = object;
-    }
-
-    controller.userData.targetRayMode = event.data.targetRayMode;
-}
-
-function onSelectEnd(event) {
-    const controller = event.target;
-
-    if (controller.userData.selected !== undefined) {
-        const object = controller.userData.selected;
-        object.material.uniforms.emissive.value = 0;
-        group.attach(object);
-
-        controller.userData.selected = undefined;
-    }
-}
-
-function getIntersections(controller) {
-    controller.updateMatrixWorld();
-
-    tempMatrix.identity().extractRotation(controller.matrixWorld);
-
-    raycaster.ray.origin.setFromMatrixPosition(controller.matrixWorld);
-    raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);
-
-    return raycaster.intersectObjects(group.children, false);
-}
-
-function intersectObjects(controller) {
-    // Do not highlight in mobile-ar
-
-    if (controller.userData.targetRayMode === 'screen') return;
-
-    // Do not highlight when already selected
-
-    if (controller.userData.selected !== undefined) return;
-
-    const line = controller.getObjectByName('line');
-    const intersections = getIntersections(controller);
-
-    if (intersections.length > 0) {
-        const intersection = intersections[0];
-
-        const object = intersection.object;
-        object.material.uniforms.emissive.value = 1;
-        intersected.push(object);
-
-        line.scale.z = intersection.distance;
-    } else {
-        line.scale.z = 5;
-    }
-}
-
-function cleanIntersected() {
-    while (intersected.length) {
-        const object = intersected.pop();
-        object.material.uniforms.emissive.value = 0;
-    }
-}
-
-//
-
-function animate() {
-    renderer.setAnimationLoop(render);
-}
-
-function render() {
-    if (renderer.xr.hasDepthSensing() && !isDepthSupplied) {
-        group.children.forEach(child => {
-            child.material.uniforms.depthColor.value = renderer.xr.getDepthTexture();
-            child.material.uniforms.depthWidth.value = 1680;
-            child.material.uniforms.depthHeight.value = 1760;
-
-            isDepthSupplied = true;
-        });
-    } else if (!renderer.xr.hasDepthSensing() && isDepthSupplied) {
-        group.children.forEach(child => {
-            child.material.uniforms.depthWidth.value = 0;
-            child.material.uniforms.depthHeight.value = 0;
-
-            isDepthSupplied = false;
-        });
-    }
-
-    cleanIntersected();
-
-    intersectObjects(controller1);
-    intersectObjects(controller2);
-
-    renderer.render(scene, camera);
-}