From 863d746cb884d22ec0e800cf090729be22149707 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 2 Jul 2023 10:58:11 -0400 Subject: [PATCH 01/20] Add initial files --- .prettierignore | 1 + examples-testing/README.md | 21 + examples-testing/changes.patch | 1751 +++++++++++++++++ examples-testing/examples/css2d_label.ts | 195 ++ examples-testing/examples/css3d_molecules.ts | 367 ++++ .../examples/css3d_orthographic.ts | 243 +++ .../examples/css3d_periodictable.ts | 802 ++++++++ examples-testing/examples/css3d_sandbox.ts | 216 ++ examples-testing/examples/css3d_sprites.ts | 170 ++ examples-testing/examples/css3d_youtube.ts | 87 + examples-testing/examples/games_fps.ts | 400 ++++ .../examples/misc_animation_groups.ts | 145 ++ .../examples/misc_animation_keys.ts | 162 ++ .../examples/misc_boxselection.ts | 151 ++ .../examples/misc_controls_arcball.ts | 235 +++ .../examples/misc_controls_drag.ts | 157 ++ .../examples/misc_controls_fly.ts | 263 +++ .../examples/misc_controls_map.ts | 108 + .../examples/misc_controls_orbit.ts | 100 + .../examples/misc_controls_pointerlock.ts | 283 +++ .../examples/misc_controls_trackball.ts | 143 ++ .../examples/misc_controls_transform.ts | 182 ++ .../examples/misc_exporter_draco.ts | 124 ++ .../examples/misc_exporter_gltf.ts | 539 +++++ .../examples/misc_exporter_obj.ts | 206 ++ .../examples/misc_exporter_ply.ts | 163 ++ .../examples/misc_exporter_stl.ts | 136 ++ .../examples/misc_exporter_usdz.ts | 119 ++ examples-testing/examples/misc_lookat.ts | 106 + examples-testing/examples/misc_uv_tests.ts | 71 + .../examples/physics_rapier_instancing.ts | 139 ++ examples-testing/examples/svg_lines.ts | 95 + examples-testing/examples/svg_sandbox.ts | 254 +++ .../examples/webaudio_orientation.ts | 151 ++ examples-testing/index.js | 35 + examples-testing/package.json | 16 + package.json | 9 +- yarn.lock | 44 +- 38 files changed, 8381 insertions(+), 8 deletions(-) create mode 100644 examples-testing/README.md create mode 100644 examples-testing/changes.patch 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_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_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/index.js create mode 100644 examples-testing/package.json diff --git a/.prettierignore b/.prettierignore index b598bb332..2b9ca99b9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ .pnp.* .yarn/* +examples-testing diff --git a/examples-testing/README.md b/examples-testing/README.md new file mode 100644 index 000000000..7bac75969 --- /dev/null +++ b/examples-testing/README.md @@ -0,0 +1,21 @@ +# Update patch + +- `git apply changes.patch` +- Make changes +- `yarn run type-check` +- `git diff > ../changes.patch` +- Reset changes +- Copy patch file + +# Update sources + +- Delete all examples +- `node index.js` +- Commit changes +- `git apply --reject changes.patch` +- Fix conflicts +- `yarn run type-check` +- `git diff > ../changes.patch` +- Reset changes +- Copy patch file +- Commit changes diff --git a/examples-testing/changes.patch b/examples-testing/changes.patch new file mode 100644 index 000000000..e3c64ec15 --- /dev/null +++ b/examples-testing/changes.patch @@ -0,0 +1,1751 @@ +diff --git a/examples/css2d_label.ts b/examples/css2d_label.ts +index b134c09..1ddbfdd 100644 +--- a/examples/css2d_label.ts ++++ b/examples/css2d_label.ts +@@ -10,7 +10,10 @@ import { GUI } from "three/addons/libs/lil-gui.module.min.js"; + + let gui; + +-let camera, scene, renderer, labelRenderer; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ labelRenderer: CSS2DRenderer; + + const layers = { + "Toggle Name": function () { +@@ -31,7 +34,7 @@ const layers = { + const clock = new THREE.Clock(); + const textureLoader = new THREE.TextureLoader(); + +-let moon; ++let moon: THREE.Mesh; + + init(); + animate(); +@@ -71,7 +74,7 @@ function init() { + normalMap: textureLoader.load("textures/planets/earth_normal_2048.jpg"), + normalScale: new THREE.Vector2(0.85, 0.85), + }); +- earthMaterial.map.colorSpace = THREE.SRGBColorSpace; ++ earthMaterial.map!.colorSpace = THREE.SRGBColorSpace; + const earth = new THREE.Mesh(earthGeometry, earthMaterial); + scene.add(earth); + +@@ -80,7 +83,7 @@ function init() { + shininess: 5, + map: textureLoader.load("textures/planets/moon_1024.jpg"), + }); +- moonMaterial.map.colorSpace = THREE.SRGBColorSpace; ++ moonMaterial.map!.colorSpace = THREE.SRGBColorSpace; + moon = new THREE.Mesh(moonGeometry, moonMaterial); + scene.add(moon); + +diff --git a/examples/css3d_molecules.ts b/examples/css3d_molecules.ts +index ee79a49..a73c6af 100644 +--- a/examples/css3d_molecules.ts ++++ b/examples/css3d_molecules.ts +@@ -9,11 +9,13 @@ import { + } 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; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: CSS3DRenderer; ++let controls: TrackballControls; ++let root: THREE.Object3D; + +-const objects = []; ++const objects: Array = []; + const tmpVec1 = new THREE.Vector3(); + const tmpVec2 = new THREE.Vector3(); + const tmpVec3 = new THREE.Vector3(); +@@ -53,7 +55,7 @@ const params = { + }; + + const loader = new PDBLoader(); +-const colorSpriteMap = {}; ++const colorSpriteMap: { [element: string]: string | undefined } = {}; + const baseSprite = document.createElement("img"); + + init(); +@@ -77,7 +79,7 @@ function init() { + + renderer = new CSS3DRenderer(); + renderer.setSize(window.innerWidth, window.innerHeight); +- document.getElementById("container").appendChild(renderer.domElement); ++ document.getElementById("container")!.appendChild(renderer.domElement); + + // + +@@ -105,7 +107,7 @@ function init() { + gui.open(); + } + +-function changeVizType(value) { ++function changeVizType(value: number) { + if (value === 0) showAtoms(); + else if (value === 1) showBonds(); + else showAtomsBonds(); +@@ -157,7 +159,12 @@ function showAtomsBonds() { + + // + +-function colorify(ctx, width, height, color) { ++function colorify( ++ ctx: CanvasRenderingContext2D, ++ width: number, ++ height: number, ++ color: THREE.Color ++) { + const r = color.r, + g = color.g, + b = color.b; +@@ -174,7 +181,7 @@ function colorify(ctx, width, height, color) { + ctx.putImageData(imageData, 0, 0); + } + +-function imageToCanvas(image) { ++function imageToCanvas(image: HTMLImageElement) { + const width = image.width; + const height = image.height; + +@@ -183,7 +190,7 @@ function imageToCanvas(image) { + canvas.width = width; + canvas.height = height; + +- const context = canvas.getContext("2d"); ++ const context = canvas.getContext("2d")!; + context.drawImage(image, 0, 0, width, height); + + return canvas; +@@ -191,12 +198,12 @@ function imageToCanvas(image) { + + // + +-function loadMolecule(model) { ++function loadMolecule(model: string) { + const url = "models/pdb/" + model; + + for (let i = 0; i < objects.length; i++) { + const object = objects[i]; +- object.parent.remove(object); ++ object.parent!.remove(object); + } + + objects.length = 0; +@@ -207,7 +214,7 @@ function loadMolecule(model) { + const json = pdb.json; + + geometryAtoms.computeBoundingBox(); +- geometryAtoms.boundingBox.getCenter(offset).negate(); ++ geometryAtoms.boundingBox!.getCenter(offset).negate(); + + geometryAtoms.translate(offset.x, offset.y, offset.z); + geometryBonds.translate(offset.x, offset.y, offset.z); +@@ -227,7 +234,7 @@ function loadMolecule(model) { + + if (!colorSpriteMap[element]) { + const canvas = imageToCanvas(baseSprite); +- const context = canvas.getContext("2d"); ++ const context = canvas.getContext("2d")!; + + colorify(context, canvas.width, canvas.height, color); + +@@ -236,7 +243,7 @@ function loadMolecule(model) { + colorSpriteMap[element] = dataUrl; + } + +- const colorSprite = colorSpriteMap[element]; ++ const colorSprite = colorSpriteMap[element]!; + + const atom = document.createElement("img"); + atom.src = colorSprite; +diff --git a/examples/css3d_orthographic.ts b/examples/css3d_orthographic.ts +index 715537b..bdcb398 100644 +--- a/examples/css3d_orthographic.ts ++++ b/examples/css3d_orthographic.ts +@@ -6,10 +6,13 @@ import { + CSS3DObject, + } from "three/addons/renderers/CSS3DRenderer.js"; + import { GUI } from "three/addons/libs/lil-gui.module.min.js"; ++import { Controller } from "lil-gui"; + +-let camera, scene, renderer; ++let camera: THREE.OrthographicCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + +-let scene2, renderer2; ++let scene2: THREE.Scene, renderer2: CSS3DRenderer; + + const frustumSize = 500; + +@@ -85,18 +88,24 @@ function init() { + renderer2 = new CSS3DRenderer(); + renderer2.setSize(window.innerWidth, window.innerHeight); + renderer2.domElement.style.position = "absolute"; +- renderer2.domElement.style.top = 0; ++ 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) { ++ function createPlane( ++ width: number, ++ height: number, ++ cssColor: string, ++ pos: THREE.Vector3, ++ rot: THREE.Euler ++ ) { + const element = document.createElement("div"); + element.style.width = width + "px"; + element.style.height = height + "px"; +- element.style.opacity = 0.75; ++ element.style.opacity = "0.75"; + element.style.background = cssColor; + + const object = new CSS3DObject(element); +@@ -143,12 +152,12 @@ function createPanel() { + + 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); ++ (folder1.children[1] as Controller).enable().setValue(window.innerWidth); ++ (folder1.children[2] as Controller).enable().setValue(window.innerHeight); ++ (folder1.children[3] as Controller).enable().setValue(0); ++ (folder1.children[4] as Controller).enable().setValue(0); ++ (folder1.children[5] as Controller).enable().setValue(window.innerWidth); ++ (folder1.children[6] as Controller).enable().setValue(window.innerHeight); + }, + fullWidth: 0, + fullHeight: 0, +@@ -157,12 +166,12 @@ function createPanel() { + 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(); ++ (folder1.children[1] as Controller).setValue(0).disable(); ++ (folder1.children[2] as Controller).setValue(0).disable(); ++ (folder1.children[3] as Controller).setValue(0).disable(); ++ (folder1.children[4] as Controller).setValue(0).disable(); ++ (folder1.children[5] as Controller).setValue(0).disable(); ++ (folder1.children[6] as Controller).setValue(0).disable(); + camera.clearViewOffset(); + }, + }; +@@ -176,7 +185,7 @@ function createPanel() { + window.screen.width * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ fullWidth: val })) ++ .onChange((val: number) => updateCameraViewOffset({ fullWidth: val })) + .disable(); + folder1 + .add( +@@ -186,19 +195,19 @@ function createPanel() { + window.screen.height * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ fullHeight: val })) ++ .onChange((val: number) => updateCameraViewOffset({ fullHeight: val })) + .disable(); + folder1 + .add(settings, "offsetX", 0, 256, 1) +- .onChange((val) => updateCameraViewOffset({ x: val })) ++ .onChange((val: number) => updateCameraViewOffset({ x: val })) + .disable(); + folder1 + .add(settings, "offsetY", 0, 256, 1) +- .onChange((val) => updateCameraViewOffset({ y: val })) ++ .onChange((val: number) => updateCameraViewOffset({ y: val })) + .disable(); + folder1 + .add(settings, "width", window.screen.width / 4, window.screen.width * 2, 1) +- .onChange((val) => updateCameraViewOffset({ width: val })) ++ .onChange((val: number) => updateCameraViewOffset({ width: val })) + .disable(); + folder1 + .add( +@@ -208,7 +217,7 @@ function createPanel() { + window.screen.height * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ height: val })) ++ .onChange((val: number) => updateCameraViewOffset({ height: val })) + .disable(); + folder1.add(settings, "clearViewOffset"); + } +@@ -220,6 +229,13 @@ function updateCameraViewOffset({ + y, + width, + height, ++}: { ++ fullWidth?: number; ++ fullHeight?: number; ++ x?: number; ++ y?: number; ++ width?: number; ++ height?: number; + }) { + if (!camera.view) { + camera.setViewOffset( +diff --git a/examples/css3d_periodictable.ts b/examples/css3d_periodictable.ts +index 33d3514..c439385 100644 +--- a/examples/css3d_periodictable.ts ++++ b/examples/css3d_periodictable.ts +@@ -600,11 +600,18 @@ const table = [ + 7, + ]; + +-let camera, scene, renderer; +-let controls; +- +-const objects = []; +-const targets = { table: [], sphere: [], helix: [], grid: [] }; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: CSS3DRenderer; ++let controls: TrackballControls; ++ ++const objects: CSS3DObject[] = []; ++const targets: { ++ table: THREE.Object3D[]; ++ sphere: THREE.Object3D[]; ++ helix: THREE.Object3D[]; ++ grid: THREE.Object3D[]; ++} = { table: [], sphere: [], helix: [], grid: [] }; + + init(); + animate(); +@@ -630,12 +637,12 @@ function init() { + + const number = document.createElement("div"); + number.className = "number"; +- number.textContent = i / 5 + 1; ++ number.textContent = `${i / 5 + 1}`; + element.appendChild(number); + + const symbol = document.createElement("div"); + symbol.className = "symbol"; +- symbol.textContent = table[i]; ++ symbol.textContent = table[i] as string; + element.appendChild(symbol); + + const details = document.createElement("div"); +@@ -654,8 +661,8 @@ function init() { + // + + const object = new THREE.Object3D(); +- object.position.x = table[i + 3] * 140 - 1330; +- object.position.y = -(table[i + 4] * 180) + 990; ++ object.position.x = (table[i + 3] as number) * 140 - 1330; ++ object.position.y = -((table[i + 4] as number) * 180) + 990; + + targets.table.push(object); + } +@@ -714,7 +721,7 @@ function init() { + + renderer = new CSS3DRenderer(); + renderer.setSize(window.innerWidth, window.innerHeight); +- document.getElementById("container").appendChild(renderer.domElement); ++ document.getElementById("container")!.appendChild(renderer.domElement); + + // + +@@ -723,22 +730,22 @@ function init() { + controls.maxDistance = 6000; + controls.addEventListener("change", render); + +- const buttonTable = document.getElementById("table"); ++ const buttonTable = document.getElementById("table")!; + buttonTable.addEventListener("click", function () { + transform(targets.table, 2000); + }); + +- const buttonSphere = document.getElementById("sphere"); ++ const buttonSphere = document.getElementById("sphere")!; + buttonSphere.addEventListener("click", function () { + transform(targets.sphere, 2000); + }); + +- const buttonHelix = document.getElementById("helix"); ++ const buttonHelix = document.getElementById("helix")!; + buttonHelix.addEventListener("click", function () { + transform(targets.helix, 2000); + }); + +- const buttonGrid = document.getElementById("grid"); ++ const buttonGrid = document.getElementById("grid")!; + buttonGrid.addEventListener("click", function () { + transform(targets.grid, 2000); + }); +@@ -750,7 +757,7 @@ function init() { + window.addEventListener("resize", onWindowResize); + } + +-function transform(targets, duration) { ++function transform(targets: THREE.Object3D[], duration: number) { + TWEEN.removeAll(); + + for (let i = 0; i < objects.length; i++) { +@@ -774,7 +781,7 @@ function transform(targets, duration) { + .start(); + } + +- new TWEEN.Tween(this) ++ new TWEEN.Tween({}) + .to({}, duration * 2) + .onUpdate(render) + .start(); +diff --git a/examples/css3d_sandbox.ts b/examples/css3d_sandbox.ts +index 5bf5178..2bde7f0 100644 +--- a/examples/css3d_sandbox.ts ++++ b/examples/css3d_sandbox.ts +@@ -5,13 +5,15 @@ import { + CSS3DRenderer, + CSS3DObject, + } from "three/addons/renderers/CSS3DRenderer.js"; +-import { GUI } from "three/addons/libs/lil-gui.module.min.js"; ++import { Controller, GUI } from "three/addons/libs/lil-gui.module.min.js"; + +-let camera, scene, renderer; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + +-let scene2, renderer2; ++let scene2: THREE.Scene, renderer2: CSS3DRenderer; + +-let controls; ++let controls: TrackballControls; + + init(); + animate(); +@@ -43,7 +45,7 @@ function init() { + const element = document.createElement("div"); + element.style.width = "100px"; + element.style.height = "100px"; +- element.style.opacity = i < 5 ? 0.5 : 1; ++ element.style.opacity = `${i < 5 ? 0.5 : 1}`; + element.style.background = new THREE.Color( + Math.random() * 0xffffff + ).getStyle(); +@@ -78,7 +80,7 @@ function init() { + renderer2 = new CSS3DRenderer(); + renderer2.setSize(window.innerWidth, window.innerHeight); + renderer2.domElement.style.position = "absolute"; +- renderer2.domElement.style.top = 0; ++ renderer2.domElement.style.top = "0"; + document.body.appendChild(renderer2.domElement); + + controls = new TrackballControls(camera, renderer2.domElement); +@@ -112,12 +114,12 @@ function createPanel() { + + 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); ++ (folder1.children[1] as Controller).enable().setValue(window.innerWidth); ++ (folder1.children[2] as Controller).enable().setValue(window.innerHeight); ++ (folder1.children[3] as Controller).enable().setValue(0); ++ (folder1.children[4] as Controller).enable().setValue(0); ++ (folder1.children[5] as Controller).enable().setValue(window.innerWidth); ++ (folder1.children[6] as Controller).enable().setValue(window.innerHeight); + }, + fullWidth: 0, + fullHeight: 0, +@@ -126,12 +128,12 @@ function createPanel() { + 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(); ++ (folder1.children[1] as Controller).setValue(0).disable(); ++ (folder1.children[2] as Controller).setValue(0).disable(); ++ (folder1.children[3] as Controller).setValue(0).disable(); ++ (folder1.children[4] as Controller).setValue(0).disable(); ++ (folder1.children[5] as Controller).setValue(0).disable(); ++ (folder1.children[6] as Controller).setValue(0).disable(); + camera.clearViewOffset(); + }, + }; +@@ -145,7 +147,7 @@ function createPanel() { + window.screen.width * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ fullWidth: val })) ++ .onChange((val: number) => updateCameraViewOffset({ fullWidth: val })) + .disable(); + folder1 + .add( +@@ -155,19 +157,19 @@ function createPanel() { + window.screen.height * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ fullHeight: val })) ++ .onChange((val: number) => updateCameraViewOffset({ fullHeight: val })) + .disable(); + folder1 + .add(settings, "offsetX", 0, 256, 1) +- .onChange((val) => updateCameraViewOffset({ x: val })) ++ .onChange((val: number) => updateCameraViewOffset({ x: val })) + .disable(); + folder1 + .add(settings, "offsetY", 0, 256, 1) +- .onChange((val) => updateCameraViewOffset({ y: val })) ++ .onChange((val: number) => updateCameraViewOffset({ y: val })) + .disable(); + folder1 + .add(settings, "width", window.screen.width / 4, window.screen.width * 2, 1) +- .onChange((val) => updateCameraViewOffset({ width: val })) ++ .onChange((val: number) => updateCameraViewOffset({ width: val })) + .disable(); + folder1 + .add( +@@ -177,7 +179,7 @@ function createPanel() { + window.screen.height * 2, + 1 + ) +- .onChange((val) => updateCameraViewOffset({ height: val })) ++ .onChange((val: number) => updateCameraViewOffset({ height: val })) + .disable(); + folder1.add(settings, "clearViewOffset"); + } +@@ -189,6 +191,13 @@ function updateCameraViewOffset({ + y, + width, + height, ++}: { ++ fullWidth?: number; ++ fullHeight?: number; ++ x?: number; ++ y?: number; ++ width?: number; ++ height?: number; + }) { + if (!camera.view) { + camera.setViewOffset( +diff --git a/examples/css3d_sprites.ts b/examples/css3d_sprites.ts +index ce6d238..23f3e9e 100644 +--- a/examples/css3d_sprites.ts ++++ b/examples/css3d_sprites.ts +@@ -7,12 +7,14 @@ import { + CSS3DSprite, + } from "three/addons/renderers/CSS3DRenderer.js"; + +-let camera, scene, renderer; +-let controls; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: CSS3DRenderer; ++let controls: TrackballControls; + + const particlesTotal = 512; +-const positions = []; +-const objects = []; ++const positions: number[] = []; ++const objects: CSS3DSprite[] = []; + let current = 0; + + init(); +@@ -33,7 +35,7 @@ function init() { + const image = document.createElement("img"); + image.addEventListener("load", function () { + for (let i = 0; i < particlesTotal; i++) { +- const object = new CSS3DSprite(image.cloneNode()); ++ const object = new CSS3DSprite(image.cloneNode() as typeof image); + (object.position.x = Math.random() * 4000 - 2000), + (object.position.y = Math.random() * 4000 - 2000), + (object.position.z = Math.random() * 4000 - 2000); +@@ -105,7 +107,7 @@ function init() { + + renderer = new CSS3DRenderer(); + renderer.setSize(window.innerWidth, window.innerHeight); +- document.getElementById("container").appendChild(renderer.domElement); ++ document.getElementById("container")!.appendChild(renderer.domElement); + + // + +@@ -143,7 +145,7 @@ function transition() { + .start(); + } + +- new TWEEN.Tween(this) ++ new TWEEN.Tween({}) + .to({}, duration * 3) + .onComplete(transition) + .start(); +diff --git a/examples/css3d_youtube.ts b/examples/css3d_youtube.ts +index f717a9a..4d60375 100644 +--- a/examples/css3d_youtube.ts ++++ b/examples/css3d_youtube.ts +@@ -6,10 +6,12 @@ import { + CSS3DObject, + } from "three/addons/renderers/CSS3DRenderer.js"; + +-let camera, scene, renderer; +-let controls; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: CSS3DRenderer; ++let controls: TrackballControls; + +-function Element(id, x, y, z, ry) { ++function Element(id: string, x: number, y: number, z: number, ry: number) { + const div = document.createElement("div"); + div.style.width = "480px"; + div.style.height = "360px"; +@@ -33,7 +35,7 @@ init(); + animate(); + + function init() { +- const container = document.getElementById("container"); ++ const container = document.getElementById("container")!; + + camera = new THREE.PerspectiveCamera( + 50, +@@ -50,10 +52,10 @@ function init() { + 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)); ++ group.add(Element("SJOz3qjfQXU", 0, 0, 240, 0)); ++ group.add(Element("Y2-xZ-1HE-Q", 240, 0, 0, Math.PI / 2)); ++ group.add(Element("IrydklNpcFI", 0, 0, -240, Math.PI)); ++ group.add(Element("9ubytEsCaS0", -240, 0, 0, -Math.PI / 2)); + scene.add(group); + + controls = new TrackballControls(camera, renderer.domElement); +@@ -63,7 +65,7 @@ function init() { + + // Block iframe events when dragging camera + +- const blocker = document.getElementById("blocker"); ++ const blocker = document.getElementById("blocker")!; + blocker.style.display = "none"; + + controls.addEventListener("start", function () { +diff --git a/examples/games_fps.ts b/examples/games_fps.ts +index 6dd79ea..4b80597 100644 +--- a/examples/games_fps.ts ++++ b/examples/games_fps.ts +@@ -44,7 +44,7 @@ directionalLight.shadow.radius = 4; + directionalLight.shadow.bias = -0.00006; + scene.add(directionalLight); + +-const container = document.getElementById("container"); ++const container = document.getElementById("container")!; + + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(window.devicePixelRatio); +@@ -56,9 +56,9 @@ renderer.useLegacyLights = false; + container.appendChild(renderer.domElement); + + const stats = new Stats(); +-stats.domElement.style.position = "absolute"; +-stats.domElement.style.top = "0px"; +-container.appendChild(stats.domElement); ++stats.dom.style.position = "absolute"; ++stats.dom.style.top = "0px"; ++container.appendChild(stats.dom); + + const GRAVITY = 30; + +@@ -70,7 +70,13 @@ const STEPS_PER_FRAME = 5; + const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5); + const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xdede8d }); + +-const spheres = []; ++interface Sphere { ++ mesh: THREE.Mesh; ++ collider: THREE.Sphere; ++ velocity: THREE.Vector3; ++} ++ ++const spheres: Sphere[] = []; + let sphereIdx = 0; + + for (let i = 0; i < NUM_SPHERES; i++) { +@@ -101,7 +107,7 @@ const playerDirection = new THREE.Vector3(); + let playerOnFloor = false; + let mouseTime = 0; + +-const keyStates = {}; ++const keyStates: { [eventCode: string]: boolean | undefined } = {}; + + const vector1 = new THREE.Vector3(); + const vector2 = new THREE.Vector3(); +@@ -180,7 +186,7 @@ function playerCollisions() { + } + } + +-function updatePlayer(deltaTime) { ++function updatePlayer(deltaTime: number) { + let damping = Math.exp(-4 * deltaTime) - 1; + + if (!playerOnFloor) { +@@ -200,7 +206,7 @@ function updatePlayer(deltaTime) { + camera.position.copy(playerCollider.end); + } + +-function playerSphereCollision(sphere) { ++function playerSphereCollision(sphere: Sphere) { + const center = vector1 + .addVectors(playerCollider.start, playerCollider.end) + .multiplyScalar(0.5); +@@ -263,7 +269,7 @@ function spheresCollisions() { + } + } + +-function updateSpheres(deltaTime) { ++function updateSpheres(deltaTime: number) { + spheres.forEach((sphere) => { + sphere.collider.center.addScaledVector(sphere.velocity, deltaTime); + +@@ -309,7 +315,7 @@ function getSideVector() { + return playerDirection; + } + +-function controls(deltaTime) { ++function controls(deltaTime: number) { + // gives a bit of air control + const speedDelta = deltaTime * (playerOnFloor ? 25 : 8); + +@@ -344,12 +350,14 @@ loader.load("collision-world.glb", (gltf) => { + worldOctree.fromGraphNode(gltf.scene); + + gltf.scene.traverse((child) => { +- if (child.isMesh) { ++ if ((child as THREE.Mesh).isMesh) { + child.castShadow = true; + child.receiveShadow = true; + +- if (child.material.map) { +- child.material.map.anisotropy = 4; ++ if (((child as THREE.Mesh).material as THREE.MeshStandardMaterial).map) { ++ ( ++ (child as THREE.Mesh).material as THREE.MeshStandardMaterial ++ ).map!.anisotropy = 4; + } + } + }); +@@ -359,7 +367,7 @@ loader.load("collision-world.glb", (gltf) => { + scene.add(helper); + + const gui = new GUI({ width: 200 }); +- gui.add({ debug: false }, "debug").onChange(function (value) { ++ gui.add({ debug: false }, "debug").onChange(function (value: boolean) { + helper.visible = value; + }); + +diff --git a/examples/misc_animation_groups.ts b/examples/misc_animation_groups.ts +index d8f760a..3a0e03d 100644 +--- a/examples/misc_animation_groups.ts ++++ b/examples/misc_animation_groups.ts +@@ -2,8 +2,11 @@ import * as THREE from "three"; + + import Stats from "three/addons/libs/stats.module.js"; + +-let stats, clock; +-let scene, camera, renderer, mixer; ++let stats: Stats, clock: THREE.Clock; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer, ++ mixer: THREE.AnimationMixer; + + init(); + animate(); +diff --git a/examples/misc_animation_keys.ts b/examples/misc_animation_keys.ts +index 1008498..a89efdc 100644 +--- a/examples/misc_animation_keys.ts ++++ b/examples/misc_animation_keys.ts +@@ -2,8 +2,11 @@ import * as THREE from "three"; + + import Stats from "three/addons/libs/stats.module.js"; + +-let stats, clock; +-let scene, camera, renderer, mixer; ++let stats: Stats, clock: THREE.Clock; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer, ++ mixer: THREE.AnimationMixer; + + init(); + animate(); +diff --git a/examples/misc_boxselection.ts b/examples/misc_boxselection.ts +index 9dba2bc..21b44cf 100644 +--- a/examples/misc_boxselection.ts ++++ b/examples/misc_boxselection.ts +@@ -5,81 +5,80 @@ 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; ++let container: HTMLDivElement, stats: Stats; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + +-init(); +-animate(); ++container = document.createElement("div"); ++document.body.appendChild(container); + +-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; + +- 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 = new THREE.Scene(); +- scene.background = new THREE.Color(0xf0f0f0); ++scene.add(new THREE.AmbientLight(0xaaaaaa)); + +- scene.add(new THREE.AmbientLight(0xaaaaaa)); ++const light = new THREE.SpotLight(0xffffff, 10000); ++light.position.set(0, 25, 50); ++light.angle = Math.PI / 5; + +- 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; + +- 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); + +- scene.add(light); ++const geometry = new THREE.BoxGeometry(); + +- 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 }) ++ ); + +- 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.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.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.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; + +- object.castShadow = true; +- object.receiveShadow = true; ++ scene.add(object); ++} + +- scene.add(object); +- } ++renderer = new THREE.WebGLRenderer({ antialias: true }); ++renderer.setPixelRatio(window.devicePixelRatio); ++renderer.setSize(window.innerWidth, window.innerHeight); ++renderer.useLegacyLights = false; ++renderer.shadowMap.enabled = true; ++renderer.shadowMap.type = THREE.PCFShadowMap; + +- renderer = new THREE.WebGLRenderer({ antialias: true }); +- renderer.setPixelRatio(window.devicePixelRatio); +- renderer.setSize(window.innerWidth, window.innerHeight); +- renderer.useLegacyLights = false; +- renderer.shadowMap.enabled = true; +- renderer.shadowMap.type = THREE.PCFShadowMap; ++container.appendChild(renderer.domElement); + +- container.appendChild(renderer.domElement); ++stats = new Stats(); ++container.appendChild(stats.dom); + +- stats = new Stats(); +- container.appendChild(stats.dom); ++window.addEventListener("resize", onWindowResize); + +- window.addEventListener("resize", onWindowResize); +-} ++animate(); + + function onWindowResize() { + camera.aspect = window.innerWidth / window.innerHeight; +@@ -106,7 +105,7 @@ const helper = new SelectionHelper(renderer, "selectBox"); + + document.addEventListener("pointerdown", function (event) { + for (const item of selectionBox.collection) { +- item.material.emissive.set(0x000000); ++ (item.material as THREE.MeshLambertMaterial).emissive.set(0x000000); + } + + selectionBox.startPoint.set( +@@ -119,7 +118,9 @@ document.addEventListener("pointerdown", function (event) { + 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.collection[i].material as THREE.MeshLambertMaterial ++ ).emissive.set(0x000000); + } + + selectionBox.endPoint.set( +@@ -131,7 +132,9 @@ document.addEventListener("pointermove", function (event) { + const allSelected = selectionBox.select(); + + for (let i = 0; i < allSelected.length; i++) { +- allSelected[i].material.emissive.set(0xffffff); ++ (allSelected[i].material as THREE.MeshLambertMaterial).emissive.set( ++ 0xffffff ++ ); + } + } + }); +@@ -146,6 +149,8 @@ document.addEventListener("pointerup", function (event) { + const allSelected = selectionBox.select(); + + for (let i = 0; i < allSelected.length; i++) { +- allSelected[i].material.emissive.set(0xffffff); ++ (allSelected[i].material as THREE.MeshLambertMaterial).emissive.set( ++ 0xffffff ++ ); + } + }); +diff --git a/examples/misc_controls_arcball.ts b/examples/misc_controls_arcball.ts +index 3b2f979..c11d354 100644 +--- a/examples/misc_controls_arcball.ts ++++ b/examples/misc_controls_arcball.ts +@@ -12,8 +12,12 @@ const cameraType = { type: "Perspective" }; + + const perspectiveDistance = 2.5; + const orthographicDistance = 120; +-let camera, controls, scene, renderer, gui; +-let folderOptions, folderAnimations; ++let camera: THREE.OrthographicCamera | THREE.PerspectiveCamera, ++ controls: ArcballControls, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ gui: GUI; ++let folderOptions: GUI, folderAnimations: GUI; + + const arcballGui = { + gizmoVisible: true, +@@ -108,8 +112,8 @@ function init() { + material.normalMap.wrapS = THREE.RepeatWrapping; + + group.traverse(function (child) { +- if (child.isMesh) { +- child.material = material; ++ if ((child as THREE.Mesh).isMesh) { ++ (child as THREE.Mesh).material = material; + } + }); + +@@ -189,12 +193,13 @@ function onWindowResize() { + + const halfW = perspectiveDistance * Math.tan(halfFovH); + const halfH = perspectiveDistance * Math.tan(halfFovV); +- camera.left = -halfW; +- camera.right = halfW; +- camera.top = halfH; +- camera.bottom = -halfH; ++ (camera as THREE.OrthographicCamera).left = -halfW; ++ (camera as THREE.OrthographicCamera).right = halfW; ++ (camera as THREE.OrthographicCamera).top = halfH; ++ (camera as THREE.OrthographicCamera).bottom = -halfH; + } else if (camera.type == "PerspectiveCamera") { +- camera.aspect = window.innerWidth / window.innerHeight; ++ (camera as THREE.PerspectiveCamera).aspect = ++ window.innerWidth / window.innerHeight; + } + + camera.updateProjectionMatrix(); +@@ -208,7 +213,7 @@ function render() { + renderer.render(scene, camera); + } + +-function onKeyDown(event) { ++function onKeyDown(event: KeyboardEvent) { + if (event.key === "c") { + if (event.ctrlKey || event.metaKey) { + controls.copyState(); +@@ -220,7 +225,7 @@ function onKeyDown(event) { + } + } + +-function setCamera(type) { ++function setCamera(type: string) { + if (type == "Orthographic") { + camera = makeOrthographicCamera(); + camera.position.set(0, 0, orthographicDistance); +diff --git a/examples/misc_controls_drag.ts b/examples/misc_controls_drag.ts +index 30c0d43..13a3719 100644 +--- a/examples/misc_controls_drag.ts ++++ b/examples/misc_controls_drag.ts +@@ -2,12 +2,14 @@ import * as THREE from "three"; + + import { DragControls } from "three/addons/controls/DragControls.js"; + +-let container; +-let camera, scene, renderer; +-let controls, group; ++let container: HTMLDivElement; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; ++let controls: DragControls, group: THREE.Group; + let enableSelection = false; + +-const objects = []; ++const objects: THREE.Object3D[] = []; + + const mouse = new THREE.Vector2(), + raycaster = new THREE.Raycaster(); +@@ -106,7 +108,7 @@ function onWindowResize() { + render(); + } + +-function onKeyDown(event) { ++function onKeyDown(event: KeyboardEvent) { + enableSelection = event.keyCode === 16 ? true : false; + } + +@@ -114,7 +116,7 @@ function onKeyUp() { + enableSelection = false; + } + +-function onClick(event) { ++function onClick(event: MouseEvent) { + event.preventDefault(); + + if (enableSelection === true) { +@@ -132,10 +134,14 @@ function onClick(event) { + const object = intersections[0].object; + + if (group.children.includes(object) === true) { +- object.material.emissive.set(0x000000); ++ ( ++ (object as THREE.Mesh).material as THREE.MeshLambertMaterial ++ ).emissive.set(0x000000); + scene.attach(object); + } else { +- object.material.emissive.set(0xaaaaaa); ++ ( ++ (object as THREE.Mesh).material as THREE.MeshLambertMaterial ++ ).emissive.set(0xaaaaaa); + group.attach(object); + } + +diff --git a/examples/misc_controls_fly.ts b/examples/misc_controls_fly.ts +index c14477b..e313c6a 100644 +--- a/examples/misc_controls_fly.ts ++++ b/examples/misc_controls_fly.ts +@@ -19,11 +19,18 @@ 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 composer; ++let camera: THREE.PerspectiveCamera, ++ controls: FlyControls, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ stats: Stats; ++let geometry: THREE.SphereGeometry, ++ meshPlanet: THREE.Mesh, ++ meshClouds: THREE.Mesh, ++ meshMoon: THREE.Mesh; ++let dirLight: THREE.DirectionalLight; ++ ++let composer: EffectComposer; + + const textureLoader = new THREE.TextureLoader(); + +@@ -61,7 +68,7 @@ function init() { + // y scale is negated to compensate for normal map handedness. + normalScale: new THREE.Vector2(0.85, -0.85), + }); +- materialNormalMap.map.colorSpace = THREE.SRGBColorSpace; ++ materialNormalMap.map!.colorSpace = THREE.SRGBColorSpace; + + // planet + +@@ -78,7 +85,7 @@ function init() { + map: textureLoader.load("textures/planets/earth_clouds_1024.png"), + transparent: true, + }); +- materialClouds.map.colorSpace = THREE.SRGBColorSpace; ++ materialClouds.map!.colorSpace = THREE.SRGBColorSpace; + + meshClouds = new THREE.Mesh(geometry, materialClouds); + meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale); +@@ -90,7 +97,7 @@ function init() { + const materialMoon = new THREE.MeshPhongMaterial({ + map: textureLoader.load("textures/planets/moon_1024.jpg"), + }); +- materialMoon.map.colorSpace = THREE.SRGBColorSpace; ++ materialMoon.map!.colorSpace = THREE.SRGBColorSpace; + + meshMoon = new THREE.Mesh(geometry, materialMoon); + meshMoon.position.set(radius * 5, 0, 0); +diff --git a/examples/misc_controls_map.ts b/examples/misc_controls_map.ts +index c53e3bb..eea3c22 100644 +--- a/examples/misc_controls_map.ts ++++ b/examples/misc_controls_map.ts +@@ -4,7 +4,10 @@ import { GUI } from "three/addons/libs/lil-gui.module.min.js"; + + import { MapControls } from "three/addons/controls/MapControls.js"; + +-let camera, controls, scene, renderer; ++let camera: THREE.PerspectiveCamera, ++ controls: MapControls, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + + init(); + //render(); // remove when using next line for animation loop (requestAnimationFrame) +diff --git a/examples/misc_controls_orbit.ts b/examples/misc_controls_orbit.ts +index 63aa40c..f40a7c1 100644 +--- a/examples/misc_controls_orbit.ts ++++ b/examples/misc_controls_orbit.ts +@@ -2,7 +2,10 @@ import * as THREE from "three"; + + import { OrbitControls } from "three/addons/controls/OrbitControls.js"; + +-let camera, controls, scene, renderer; ++let camera: THREE.PerspectiveCamera, ++ controls: OrbitControls, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + + init(); + //render(); // remove when using next line for animation loop (requestAnimationFrame) +diff --git a/examples/misc_controls_pointerlock.ts b/examples/misc_controls_pointerlock.ts +index 993ccde..484d674 100644 +--- a/examples/misc_controls_pointerlock.ts ++++ b/examples/misc_controls_pointerlock.ts +@@ -2,11 +2,14 @@ import * as THREE from "three"; + + import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; + +-let camera, scene, renderer, controls; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ controls: PointerLockControls; + +-const objects = []; ++const objects: THREE.Mesh[] = []; + +-let raycaster; ++let raycaster: THREE.Raycaster; + + let moveForward = false; + let moveBackward = false; +@@ -42,8 +45,8 @@ function init() { + + controls = new PointerLockControls(camera, document.body); + +- const blocker = document.getElementById("blocker"); +- const instructions = document.getElementById("instructions"); ++ const blocker = document.getElementById("blocker")!; ++ const instructions = document.getElementById("instructions")!; + + instructions.addEventListener("click", function () { + controls.lock(); +@@ -61,7 +64,7 @@ function init() { + + scene.add(controls.getObject()); + +- const onKeyDown = function (event) { ++ const onKeyDown = function (event: KeyboardEvent) { + switch (event.code) { + case "ArrowUp": + case "KeyW": +@@ -90,7 +93,7 @@ function init() { + } + }; + +- const onKeyUp = function (event) { ++ const onKeyUp = function (event: KeyboardEvent) { + switch (event.code) { + case "ArrowUp": + case "KeyW": +@@ -126,7 +129,12 @@ function init() { + + // floor + +- let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100); ++ let floorGeometry: THREE.BufferGeometry = new THREE.PlaneGeometry( ++ 2000, ++ 2000, ++ 100, ++ 100 ++ ); + floorGeometry.rotateX(-Math.PI / 2); + + // vertex displacement +diff --git a/examples/misc_controls_trackball.ts b/examples/misc_controls_trackball.ts +index 833ec6a..c679270 100644 +--- a/examples/misc_controls_trackball.ts ++++ b/examples/misc_controls_trackball.ts +@@ -5,7 +5,12 @@ 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; ++let perspectiveCamera: THREE.PerspectiveCamera, ++ orthographicCamera: THREE.OrthographicCamera, ++ controls: TrackballControls, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ stats: Stats; + + const params = { + orthographicCamera: false, +@@ -84,7 +89,7 @@ function init() { + gui + .add(params, "orthographicCamera") + .name("use orthographic") +- .onChange(function (value) { ++ .onChange(function (value: boolean) { + controls.dispose(); + + createControls(value ? orthographicCamera : perspectiveCamera); +@@ -97,7 +102,7 @@ function init() { + createControls(perspectiveCamera); + } + +-function createControls(camera) { ++function createControls(camera: THREE.Camera) { + controls = new TrackballControls(camera, renderer.domElement); + + controls.rotateSpeed = 1.0; +diff --git a/examples/misc_controls_transform.ts b/examples/misc_controls_transform.ts +index 73ff9f9..882ae39 100644 +--- a/examples/misc_controls_transform.ts ++++ b/examples/misc_controls_transform.ts +@@ -3,8 +3,13 @@ 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; ++let cameraPersp: THREE.PerspectiveCamera, ++ cameraOrtho: THREE.OrthographicCamera, ++ currentCamera: THREE.Camera; ++let scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ control: TransformControls, ++ orbit: OrbitControls; + + init(); + render(); +@@ -94,7 +99,8 @@ function init() { + case 67: // C + const position = currentCamera.position.clone(); + +- currentCamera = currentCamera.isPerspectiveCamera ++ currentCamera = (currentCamera as THREE.PerspectiveCamera) ++ .isPerspectiveCamera + ? cameraOrtho + : cameraPersp; + currentCamera.position.copy(position); +diff --git a/examples/misc_exporter_draco.ts b/examples/misc_exporter_draco.ts +index ee28b4a..b148f83 100644 +--- a/examples/misc_exporter_draco.ts ++++ b/examples/misc_exporter_draco.ts +@@ -4,7 +4,11 @@ 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; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer, ++ exporter: DRACOExporter, ++ mesh: THREE.Mesh; + + const params = { + export: exportFile, +@@ -113,12 +117,12 @@ const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + +-function save(blob, filename) { ++function save(blob: Blob, filename: string) { + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + } + +-function saveArrayBuffer(buffer, filename) { ++function saveArrayBuffer(buffer: BufferSource, filename: string) { + save(new Blob([buffer], { type: "application/octet-stream" }), filename); + } +diff --git a/examples/misc_exporter_gltf.ts b/examples/misc_exporter_gltf.ts +index 354f66f..8fdd8d4 100644 +--- a/examples/misc_exporter_gltf.ts ++++ b/examples/misc_exporter_gltf.ts +@@ -6,7 +6,7 @@ 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) { ++function exportGLTF(input: THREE.Object3D | THREE.Object3D[]) { + const gltfExporter = new GLTFExporter(); + + const options = { +@@ -37,7 +37,7 @@ const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); // Firefox workaround, see #6594 + +-function save(blob, filename) { ++function save(blob: Blob, filename: string) { + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); +@@ -45,18 +45,28 @@ function save(blob, filename) { + // URL.revokeObjectURL( url ); breaks Firefox... + } + +-function saveString(text, filename) { ++function saveString(text: string, filename: string) { + save(new Blob([text], { type: "text/plain" }), filename); + } + +-function saveArrayBuffer(buffer, filename) { ++function saveArrayBuffer(buffer: BufferSource, filename: string) { + 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; ++let container: HTMLDivElement; ++ ++let camera: THREE.PerspectiveCamera, ++ object: THREE.Object3D, ++ object2: THREE.Mesh, ++ material: THREE.MeshBasicMaterial | THREE.MeshStandardMaterial, ++ geometry: THREE.BufferGeometry, ++ scene1: THREE.Scene, ++ scene2: THREE.Scene, ++ renderer: THREE.WebGLRenderer; ++let gridHelper: THREE.GridHelper, ++ sphere: THREE.Mesh, ++ model: THREE.Group, ++ coffeemat: THREE.Group; + + const params = { + trs: false, +diff --git a/examples/misc_exporter_obj.ts b/examples/misc_exporter_obj.ts +index 9c74c6b..a469035 100644 +--- a/examples/misc_exporter_obj.ts ++++ b/examples/misc_exporter_obj.ts +@@ -4,7 +4,9 @@ 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; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + + const params = { + addTriangle: addTriangle, +@@ -72,12 +74,12 @@ function exportToObj() { + saveString(result, "object.obj"); + } + +-function addGeometry(type) { ++function addGeometry(type: number) { + for (let i = 0; i < scene.children.length; i++) { + const child = scene.children[i]; + +- if (child.isMesh || child.isPoints) { +- child.geometry.dispose(); ++ if ((child as THREE.Mesh).isMesh || (child as THREE.Points).isPoints) { ++ (child as THREE.Mesh | THREE.Points).geometry.dispose(); + scene.remove(child); + i--; + } +@@ -165,13 +167,13 @@ const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + +-function save(blob, filename) { ++function save(blob: Blob, filename: string) { + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + } + +-function saveString(text, filename) { ++function saveString(text: string, filename: string) { + save(new Blob([text], { type: "text/plain" }), filename); + } + +diff --git a/examples/misc_exporter_ply.ts b/examples/misc_exporter_ply.ts +index eb04754..f403ecd 100644 +--- a/examples/misc_exporter_ply.ts ++++ b/examples/misc_exporter_ply.ts +@@ -4,7 +4,11 @@ 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; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer, ++ exporter: PLYExporter, ++ mesh: THREE.Mesh; + + const params = { + exportASCII: exportASCII, +@@ -148,16 +152,16 @@ const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + +-function save(blob, filename) { ++function save(blob: Blob, filename: string) { + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + } + +-function saveString(text, filename) { ++function saveString(text: string, filename: string) { + save(new Blob([text], { type: "text/plain" }), filename); + } + +-function saveArrayBuffer(buffer, filename) { ++function saveArrayBuffer(buffer: BufferSource, filename: string) { + save(new Blob([buffer], { type: "application/octet-stream" }), filename); + } +diff --git a/examples/misc_exporter_stl.ts b/examples/misc_exporter_stl.ts +index 4d5ab46..bbfcbfb 100644 +--- a/examples/misc_exporter_stl.ts ++++ b/examples/misc_exporter_stl.ts +@@ -4,7 +4,11 @@ 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; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer, ++ exporter: STLExporter, ++ mesh: THREE.Mesh; + + const params = { + exportASCII: exportASCII, +@@ -121,16 +125,16 @@ const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + +-function save(blob, filename) { ++function save(blob: Blob, filename: string) { + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + } + +-function saveString(text, filename) { ++function saveString(text: string, filename: string) { + save(new Blob([text], { type: "text/plain" }), filename); + } + +-function saveArrayBuffer(buffer, filename) { ++function saveArrayBuffer(buffer: BufferSource, filename: string) { + save(new Blob([buffer], { type: "application/octet-stream" }), filename); + } +diff --git a/examples/misc_exporter_usdz.ts b/examples/misc_exporter_usdz.ts +index 833b4f0..8ee7a60 100644 +--- a/examples/misc_exporter_usdz.ts ++++ b/examples/misc_exporter_usdz.ts +@@ -6,7 +6,9 @@ import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js"; + import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; + import { USDZExporter } from "three/addons/exporters/USDZExporter.js"; + +-let camera, scene, renderer; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer; + + init(); + render(); +@@ -54,7 +56,7 @@ function init() { + const arraybuffer = await exporter.parse(gltf.scene); + const blob = new Blob([arraybuffer], { type: "application/octet-stream" }); + +- const link = document.getElementById("link"); ++ const link = document.getElementById("link") as HTMLAnchorElement; + link.href = URL.createObjectURL(blob); + }); + +@@ -73,7 +75,7 @@ function createSpotShadowMesh() { + canvas.width = 128; + canvas.height = 128; + +- const context = canvas.getContext("2d"); ++ const context = canvas.getContext("2d")!; + const gradient = context.createRadialGradient( + canvas.width / 2, + canvas.height / 2, +diff --git a/examples/misc_lookat.ts b/examples/misc_lookat.ts +index ddb6f6a..cc7d0f1 100644 +--- a/examples/misc_lookat.ts ++++ b/examples/misc_lookat.ts +@@ -2,9 +2,12 @@ import * as THREE from "three"; + + import Stats from "three/addons/libs/stats.module.js"; + +-let camera, scene, renderer, stats; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ stats: Stats; + +-let sphere; ++let sphere: THREE.Mesh; + + let mouseX = 0, + mouseY = 0; +@@ -73,7 +76,7 @@ function onWindowResize() { + renderer.setSize(window.innerWidth, window.innerHeight); + } + +-function onDocumentMouseMove(event) { ++function onDocumentMouseMove(event: MouseEvent) { + mouseX = (event.clientX - windowHalfX) * 10; + mouseY = (event.clientY - windowHalfY) * 10; + } +diff --git a/examples/misc_uv_tests.ts b/examples/misc_uv_tests.ts +index 6dd3fbf..39c2ce2 100644 +--- a/examples/misc_uv_tests.ts ++++ b/examples/misc_uv_tests.ts +@@ -7,7 +7,7 @@ import { UVsDebug } from "three/addons/utils/UVsDebug.js"; + * as well as allow a new user to visualize what UVs are about. + */ + +-function test(name, geometry) { ++function test(name: string, geometry: THREE.BufferGeometry) { + const d = document.createElement("div"); + + d.innerHTML = "

" + name + "

"; +diff --git a/examples/physics_rapier_instancing.ts b/examples/physics_rapier_instancing.ts +index 0976b54..347da19 100644 +--- a/examples/physics_rapier_instancing.ts ++++ b/examples/physics_rapier_instancing.ts +@@ -1,12 +1,18 @@ + import * as THREE from "three"; + import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +-import { RapierPhysics } from "three/addons/physics/RapierPhysics.js"; ++import { ++ RapierPhysics, ++ RapierPhysicsObject, ++} from "three/addons/physics/RapierPhysics.js"; + import Stats from "three/addons/libs/stats.module.js"; + +-let camera, scene, renderer, stats; +-let physics, position; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGLRenderer, ++ stats: Stats; ++let physics: RapierPhysicsObject, position: THREE.Vector3; + +-let boxes, spheres; ++let boxes: THREE.InstancedMesh, spheres: THREE.InstancedMesh; + + init(); + +diff --git a/examples/svg_lines.ts b/examples/svg_lines.ts +index badcf87..6662ad4 100644 +--- a/examples/svg_lines.ts ++++ b/examples/svg_lines.ts +@@ -4,7 +4,7 @@ import { SVGRenderer } from "three/addons/renderers/SVGRenderer.js"; + + THREE.ColorManagement.enabled = false; + +-let camera, scene, renderer; ++let camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: SVGRenderer; + + init(); + animate(); +diff --git a/examples/svg_sandbox.ts b/examples/svg_sandbox.ts +index e7221f3..5e3ed6e 100644 +--- a/examples/svg_sandbox.ts ++++ b/examples/svg_sandbox.ts +@@ -6,9 +6,12 @@ import { SVGRenderer, SVGObject } from "three/addons/renderers/SVGRenderer.js"; + + THREE.ColorManagement.enabled = false; + +-let camera, scene, renderer, stats; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: SVGRenderer, ++ stats: Stats; + +-let group; ++let group: THREE.Mesh; + + init(); + animate(); +@@ -41,7 +44,7 @@ function init() { + + const boxGeometry = new THREE.BoxGeometry(100, 100, 100); + +- let mesh = new THREE.Mesh( ++ let mesh: THREE.Mesh = new THREE.Mesh( + boxGeometry, + new THREE.MeshBasicMaterial({ + color: 0x0000ff, +@@ -181,7 +184,7 @@ function init() { + node.setAttribute("r", "40"); + + for (let i = 0; i < 50; i++) { +- const object = new SVGObject(node.cloneNode()); ++ const object = new SVGObject(node.cloneNode() as SVGCircleElement); + object.position.x = Math.random() * 1000 - 500; + object.position.y = Math.random() * 1000 - 500; + object.position.z = Math.random() * 1000 - 500; +@@ -194,7 +197,7 @@ function init() { + 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"); ++ const doc = parser.parseFromString(svg as string, "image/svg+xml"); + + node.appendChild(doc.documentElement); + +diff --git a/examples/webaudio_orientation.ts b/examples/webaudio_orientation.ts +index d265822..c967b7d 100644 +--- a/examples/webaudio_orientation.ts ++++ b/examples/webaudio_orientation.ts +@@ -4,16 +4,18 @@ 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; ++let scene: THREE.Scene, ++ camera: THREE.PerspectiveCamera, ++ renderer: THREE.WebGLRenderer; + +-const startButton = document.getElementById("startButton"); ++const startButton = document.getElementById("startButton")!; + startButton.addEventListener("click", init); + + function init() { +- const overlay = document.getElementById("overlay"); ++ const overlay = document.getElementById("overlay")!; + overlay.remove(); + +- const container = document.getElementById("container"); ++ const container = document.getElementById("container")!; + + // + +@@ -70,7 +72,7 @@ function init() { + const listener = new THREE.AudioListener(); + camera.add(listener); + +- const audioElement = document.getElementById("music"); ++ const audioElement = document.getElementById("music") as HTMLAudioElement; + audioElement.play(); + + const positionalAudio = new THREE.PositionalAudio(listener); +@@ -90,10 +92,11 @@ function init() { + 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; ++ if ((object as THREE.Mesh).isMesh) { ++ ((object as THREE.Mesh).material as THREE.MeshStandardMaterial).envMap = ++ reflectionCube; ++ (object as THREE.Mesh).geometry.rotateY(-Math.PI); ++ (object as THREE.Mesh).castShadow = true; + } + }); + diff --git a/examples-testing/examples/css2d_label.ts b/examples-testing/examples/css2d_label.ts new file mode 100644 index 000000000..b134c0978 --- /dev/null +++ b/examples-testing/examples/css2d_label.ts @@ -0,0 +1,195 @@ +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); + renderer.useLegacyLights = false; + 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..ee79a4937 --- /dev/null +++ b/examples-testing/examples/css3d_molecules.ts @@ -0,0 +1,367 @@ +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..715537bd2 --- /dev/null +++ b/examples-testing/examples/css3d_orthographic.ts @@ -0,0 +1,243 @@ +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); + renderer.useLegacyLights = false; + 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..33d3514bb --- /dev/null +++ b/examples-testing/examples/css3d_periodictable.ts @@ -0,0 +1,802 @@ +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] + "
" + 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..5bf51786c --- /dev/null +++ b/examples-testing/examples/css3d_sandbox.ts @@ -0,0 +1,216 @@ +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); + renderer.useLegacyLights = false; + 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..ce6d23842 --- /dev/null +++ b/examples-testing/examples/css3d_sprites.ts @@ -0,0 +1,170 @@ +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..f717a9a59 --- /dev/null +++ b/examples-testing/examples/css3d_youtube.ts @@ -0,0 +1,87 @@ +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..6dd79ea61 --- /dev/null +++ b/examples-testing/examples/games_fps.ts @@ -0,0 +1,400 @@ +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.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.VSMShadowMap; +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.useLegacyLights = false; +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) + ); + } + + 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; + }); + + animate(); +}); + +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(); + + requestAnimationFrame(animate); +} diff --git a/examples-testing/examples/misc_animation_groups.ts b/examples-testing/examples/misc_animation_groups.ts new file mode 100644 index 000000000..d8f760a39 --- /dev/null +++ b/examples-testing/examples/misc_animation_groups.ts @@ -0,0 +1,145 @@ +import * as THREE from "three"; + +import Stats from "three/addons/libs/stats.module.js"; + +let stats, clock; +let scene, camera, renderer, mixer; + +init(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + render(); +} + +function render() { + 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..10084980d --- /dev/null +++ b/examples-testing/examples/misc_animation_keys.ts @@ -0,0 +1,162 @@ +import * as THREE from "three"; + +import Stats from "three/addons/libs/stats.module.js"; + +let stats, clock; +let scene, camera, renderer, mixer; + +init(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + render(); +} + +function render() { + 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..9dba2bcc6 --- /dev/null +++ b/examples-testing/examples/misc_boxselection.ts @@ -0,0 +1,151 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + render(); + stats.update(); +} + +function render() { + renderer.render(scene, camera); +} + +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..3b2f97969 --- /dev/null +++ b/examples-testing/examples/misc_controls_arcball.ts @@ -0,0 +1,235 @@ +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.useLegacyLights = false; + 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..30c0d43e1 --- /dev/null +++ b/examples-testing/examples/misc_controls_drag.ts @@ -0,0 +1,157 @@ +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.useLegacyLights = false; + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFShadowMap; + + container.appendChild(renderer.domElement); + + controls = new DragControls([...objects], camera, renderer.domElement); + 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; +} + +function onKeyUp() { + enableSelection = false; +} + +function onClick(event) { + event.preventDefault(); + + if (enableSelection === true) { + const draggableObjects = controls.getObjects(); + 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..c14477b8c --- /dev/null +++ b/examples-testing/examples/misc_controls_fly.ts @@ -0,0 +1,263 @@ +import * as THREE from "three"; + +import Stats from "three/addons/libs/stats.module.js"; + +import { FlyControls } from "three/addons/controls/FlyControls.js"; +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; +import { FilmPass } from "three/addons/postprocessing/FilmPass.js"; +import { OutputPass } from "three/addons/postprocessing/OutputPass.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 composer; + +const textureLoader = new THREE.TextureLoader(); + +let d, dPlanet, dMoon; +const dMoonVec = new THREE.Vector3(); + +const clock = new THREE.Clock(); + +init(); +animate(); + +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, + size: 2, + sizeAttenuation: false, + }), + new THREE.PointsMaterial({ + color: 0x9c9c9c, + size: 1, + sizeAttenuation: false, + }), + new THREE.PointsMaterial({ + color: 0x7c7c7c, + size: 2, + sizeAttenuation: false, + }), + new THREE.PointsMaterial({ + color: 0x838383, + size: 1, + sizeAttenuation: false, + }), + new THREE.PointsMaterial({ + color: 0x5a5a5a, + size: 2, + sizeAttenuation: false, + }), + new THREE.PointsMaterial({ + color: 0x5a5a5a, + size: 1, + sizeAttenuation: false, + }), + ]; + + for (let i = 10; i < 30; i++) { + const stars = new THREE.Points(starsGeometry[i % 2], starsMaterials[i % 6]); + + 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.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); + renderer.useLegacyLights = false; + 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 + + const renderModel = new RenderPass(scene, camera); + const effectFilm = new FilmPass(0.35, 0.75, 2048, false); + const outputPass = new OutputPass(); + + composer = new EffectComposer(renderer); + + composer.addPass(renderModel); + composer.addPass(effectFilm); + composer.addPass(outputPass); +} + +function onWindowResize() { + SCREEN_HEIGHT = window.innerHeight; + SCREEN_WIDTH = window.innerWidth; + + camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT; + camera.updateProjectionMatrix(); + + renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); + composer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); +} + +function animate() { + requestAnimationFrame(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); + + composer.render(delta); +} diff --git a/examples-testing/examples/misc_controls_map.ts b/examples-testing/examples/misc_controls_map.ts new file mode 100644 index 000000000..c53e3bb20 --- /dev/null +++ b/examples-testing/examples/misc_controls_map.ts @@ -0,0 +1,108 @@ +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 next line for animation loop (requestAnimationFrame) +animate(); + +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.useLegacyLights = false; + 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, "screenSpacePanning"); +} + +function onWindowResize() { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); +} + +function animate() { + requestAnimationFrame(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..63aa40cab --- /dev/null +++ b/examples-testing/examples/misc_controls_orbit.ts @@ -0,0 +1,100 @@ +import * as THREE from "three"; + +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; + +let camera, controls, scene, renderer; + +init(); +//render(); // remove when using next line for animation loop (requestAnimationFrame) +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(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..993ccdec8 --- /dev/null +++ b/examples-testing/examples/misc_controls_pointerlock.ts @@ -0,0 +1,283 @@ +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(); +animate(); + +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.getObject()); + + 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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + const time = performance.now(); + + if (controls.isLocked === true) { + raycaster.ray.origin.copy(controls.getObject().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.getObject().position.y += velocity.y * delta; // new behavior + + if (controls.getObject().position.y < 10) { + velocity.y = 0; + controls.getObject().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..833ec6a2f --- /dev/null +++ b/examples-testing/examples/misc_controls_trackball.ts @@ -0,0 +1,143 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + controls.update(); + + stats.update(); + + render(); +} + +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..73ff9f9f7 --- /dev/null +++ b/examples-testing/examples/misc_controls_transform.ts @@ -0,0 +1,182 @@ +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); + renderer.useLegacyLights = false; + document.body.appendChild(renderer.domElement); + + const aspect = window.innerWidth / window.innerHeight; + + cameraPersp = new THREE.PerspectiveCamera(50, aspect, 0.01, 30000); + cameraOrtho = new THREE.OrthographicCamera( + -600 * aspect, + 600 * aspect, + 600, + -600, + 0.01, + 30000 + ); + 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.keyCode) { + case 81: // Q + control.setSpace(control.space === "local" ? "world" : "local"); + break; + + case 16: // Shift + control.setTranslationSnap(100); + control.setRotationSnap(THREE.MathUtils.degToRad(15)); + control.setScaleSnap(0.25); + break; + + case 87: // W + control.setMode("translate"); + break; + + case 69: // E + control.setMode("rotate"); + break; + + case 82: // R + control.setMode("scale"); + break; + + case 67: // 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 86: // 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 187: + case 107: // +, =, num+ + control.setSize(control.size + 0.1); + break; + + case 189: + case 109: // -, _, num- + control.setSize(Math.max(control.size - 0.1, 0.1)); + break; + + case 88: // X + control.showX = !control.showX; + break; + + case 89: // Y + control.showY = !control.showY; + break; + + case 90: // Z + control.showZ = !control.showZ; + break; + + case 32: // Spacebar + control.enabled = !control.enabled; + break; + + case 27: // Esc + control.reset(); + break; + } + }); + + window.addEventListener("keyup", function (event) { + switch (event.keyCode) { + case 16: // 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..ee28b4a25 --- /dev/null +++ b/examples-testing/examples/misc_exporter_draco.ts @@ -0,0 +1,124 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(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_gltf.ts b/examples-testing/examples/misc_exporter_gltf.ts new file mode 100644 index 000000000..354f66f52 --- /dev/null +++ b/examples-testing/examples/misc_exporter_gltf.ts @@ -0,0 +1,539 @@ +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(); +animate(); + +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; + 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); + }); + + // --------------------------------------------------------------------- + // 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.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + + render(); +} + +function render() { + 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..9c74c6b7d --- /dev/null +++ b/examples-testing/examples/misc_exporter_obj.ts @@ -0,0 +1,206 @@ +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(); +animate(); + +function init() { + renderer = new THREE.WebGLRenderer(); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.useLegacyLights = false; + 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() { + requestAnimationFrame(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..eb04754b8 --- /dev/null +++ b/examples-testing/examples/misc_exporter_ply.ts @@ -0,0 +1,163 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(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..4d5ab46fb --- /dev/null +++ b/examples-testing/examples/misc_exporter_stl.ts @@ -0,0 +1,136 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(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..833b4f073 --- /dev/null +++ b/examples-testing/examples/misc_exporter_usdz.ts @@ -0,0 +1,119 @@ +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"; + +let camera, scene, renderer; + +init(); +render(); + +function init() { + renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.useLegacyLights = false; + 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(renderer), + 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.parse(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); +} + +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 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..ddb6f6a2a --- /dev/null +++ b/examples-testing/examples/misc_lookat.ts @@ -0,0 +1,106 @@ +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(); +animate(); + +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.useLegacyLights = false; + 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() { + requestAnimationFrame(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..6dd3fbfc6 --- /dev/null +++ b/examples-testing/examples/misc_uv_tests.ts @@ -0,0 +1,71 @@ +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 = "

" + name + "

"; + + 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_rapier_instancing.ts b/examples-testing/examples/physics_rapier_instancing.ts new file mode 100644 index 000000000..0976b545b --- /dev/null +++ b/examples-testing/examples/physics_rapier_instancing.ts @@ -0,0 +1,139 @@ +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; + scene.add(floor); + physics.addMesh(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; + 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())); + } + + physics.addMesh(boxes, 1); + + // 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; + 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.addMesh(spheres, 1); + + // + + renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.useLegacyLights = false; + 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(); + + animate(); + + 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() { + requestAnimationFrame(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..badcf87e3 --- /dev/null +++ b/examples-testing/examples/svg_lines.ts @@ -0,0 +1,95 @@ +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..e7221f350 --- /dev/null +++ b/examples-testing/examples/svg_sandbox.ts @@ -0,0 +1,254 @@ +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..d265822d4 --- /dev/null +++ b/examples-testing/examples/webaudio_orientation.ts @@ -0,0 +1,151 @@ +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); + 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; + renderer.useLegacyLights = false; + 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() { + requestAnimationFrame(animate); + renderer.render(scene, camera); +} diff --git a/examples-testing/index.js b/examples-testing/index.js new file mode 100644 index 000000000..1e43c1f95 --- /dev/null +++ b/examples-testing/index.js @@ -0,0 +1,35 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import { format } from 'prettier'; + +const re = /