diff options
| author | patrick-scho | 2025-04-17 22:22:14 +0200 |
|---|---|---|
| committer | patrick-scho | 2025-04-17 22:22:14 +0200 |
| commit | 959c350fb8cddf7d1b31907fcc1f7f99dad52f3e (patch) | |
| tree | ce2f5e05082aa0536bad3d87dce424a3d3b2db93 /js/main.js | |
| parent | f97f4ee25759ffaa6a4d4709f45fc8b7b5b24973 (diff) | |
| download | cloth_sim-959c350fb8cddf7d1b31907fcc1f7f99dad52f3e.tar.gz cloth_sim-959c350fb8cddf7d1b31907fcc1f7f99dad52f3e.zip | |
Diffstat (limited to 'js/main.js')
| -rw-r--r-- | js/main.js | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..64900d3 --- /dev/null +++ b/js/main.js @@ -0,0 +1,210 @@ +
+
+function addLights(scene){
+ scene.add(new THREE.AmbientLight(0x222222));
+
+ const light1 = new THREE.PointLight(0xffffff, 1, 50);
+ light1.position.set(15, 1, 40);
+ scene.add(light1);
+
+ const light2 = new THREE.PointLight(0xffffff, 1, 50);
+ light2.position.set(-15, 0, 40);
+ scene.add(light2);
+
+ const light3 = new THREE.PointLight(0xffffff, 1, 50);
+ light3.position.set(0, -1, 40);
+ scene.add(light3);
+}
+
+/**
+ * setup THREE JS Scene, Camera and Renderer
+ */
+function setup_scene(canvasSpace) {
+ const scene = new THREE.Scene();
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / (window.innerHeight - canvasSpace), 0.1, 1000);
+ const renderer = new THREE.WebGLRenderer();
+ /** size canvas to leave some space for UI */
+ renderer.setSize(window.innerWidth, window.innerHeight - canvasSpace);
+ renderer.antialias = true;
+ /** embed canvas in HTML */
+ document.getElementById("threejscontainer").appendChild(renderer.domElement);
+
+ /** add orbit controls */
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
+ controls.target.set(0, 0, 0);
+ controls.update();
+
+ /** add scene background */
+ const loader = new THREE.TextureLoader();
+ const texture = loader.load(
+ '/projects/cloth/img/tears_of_steel_bridge_2k.jpg',
+ () => {
+ const rt = new THREE.WebGLCubeRenderTarget(texture.image.height);
+ rt.fromEquirectangularTexture(renderer, texture);
+ scene.background = rt;
+ });
+
+ /** add flag pole */
+ const geometry = new THREE.CylinderGeometry(0.02, 0.02, 5, 32);
+ const material = new THREE.MeshStandardMaterial({color: 0xffffff});
+ const cylinder = new THREE.Mesh(geometry, material);
+ cylinder.position.set(-0.5, -2.25, 0);
+ scene.add(cylinder);
+
+ /** add global light */
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+ scene.add(directionalLight);
+
+ /** position camera */
+ camera.position.z = 2;
+ addLights(scene);
+ return [scene, camera, renderer];
+}
+
+/** call "init" when document is fully loaded */
+document.body.onload = init;
+
+function init() {
+ let mousePos = new THREE.Vector2();
+
+ /**
+ * Space left empty under canvas
+ * for UI elements
+ */
+ const canvasSpace = 200;
+
+ /** Constant Frame Time */
+ const frameTime = 1000.0 / 200.0;
+
+ /** Setup scene */
+ let [scene, camera, renderer] = setup_scene(canvasSpace);
+
+ const loader = new THREE.TextureLoader();
+ //Red color: 0xC70039
+
+ /** Create cloth and generate mesh */
+ const cloth = new Cloth(1, 0.5, 20, 20);
+ const clothGeometry = cloth.generateGeometry();
+ const clothMaterial = new THREE.MeshStandardMaterial({ map: loader.load('/projects/cloth/img/hsrm2.png'), color: 0xffffff, side: THREE.DoubleSide, flatShading: false });
+ //const clothMaterial = new THREE.MeshStandardMaterial({ color: 0xC70039, side: THREE.DoubleSide, flatShading: false });
+ const clothMesh = new THREE.Mesh(clothGeometry, clothMaterial);
+ scene.add(clothMesh);
+
+ /** Register wind checkbox */
+ document.getElementById("windToggle").checked = options.wind;
+ document.getElementById("windToggle").onchange = (e) => {
+ options.wind = e.target.checked;
+ };
+
+ let raycaster = new THREE.Raycaster();
+ let intersects;
+ let windKeyDown = false;
+ let dragKeyDown = false;
+ let draggedIndex = -1;
+ /**
+ * function called every frame
+ * @param {number} dt - time passed since last frame in ms
+ */
+ function animate(dt) {
+ /** run simulation step */
+ cloth.simulate(dt / 1000);
+
+ cloth.updateGeometry(clothGeometry);
+
+ /** raycast from camera to cursor */
+ raycaster.setFromCamera(new THREE.Vector2((mousePos.x / w) * 2 - 1, ((h - mousePos.y) / h) * 2 - 1), camera);
+ intersects = raycaster.intersectObject(clothMesh);
+
+ /** provide user interaction */
+ if (intersects.length > 0) {
+ if (windKeyDown)
+ cloth.blow(camera.position, intersects);
+ /** "grab" vertex index */
+ if (dragKeyDown && draggedIndex == -1)
+ draggedIndex = intersects[0].face.a;
+ }
+ if (dragKeyDown && draggedIndex != -1)
+ cloth.drag(calculateMousePosToWorld(mousePos), draggedIndex);
+
+ /** queue next frame */
+ setTimeout(() => {
+ animate(frameTime);
+ }, frameTime);
+
+ renderer.render(scene, camera);
+ }
+
+
+ /** add callback for window resize */
+ let canvas = document.getElementsByTagName("canvas")[0];
+ let w = window.innerWidth;
+ let h = window.innerHeight - canvasSpace;
+ let resize = function () {
+ w = window.innerWidth;
+ h = window.innerHeight - canvasSpace;
+ canvas.width = w;
+ canvas.height = h;
+ }
+ window.onresize = resize;
+ resize();
+
+ /**
+ * if canvas has been successfully initialized
+ * start rendering
+ */
+ if (canvas.getContext) {
+ animate(frameTime);
+ }
+
+
+
+ /** add mouse move callback */
+ canvas.onmousemove = (evt) => {
+ mousePos.x = evt.clientX;
+ mousePos.y = evt.clientY;
+ };
+
+ /**
+ * Prevent context menu while blowing wind
+ */
+ canvas.addEventListener('contextmenu', function(evt) {
+ evt.preventDefault();
+ }, false);
+
+ /** register key events */
+ document.onkeydown = (evt) => {
+ if (evt.code === "KeyW")
+ windKeyDown = true;
+ if (evt.code === "KeyD")
+ dragKeyDown = true;
+ };
+
+ document.onkeyup = (evt) => {
+ if (evt.code === "KeyW")
+ windKeyDown = false;
+ if (evt.code === "KeyD") {
+ dragKeyDown = false;
+ draggedIndex = -1;
+ }
+ };
+
+ /** helper function to turn mouse position into 3D coordinates */
+ function calculateMousePosToWorld(mousePos){
+ var vec = new THREE.Vector3(); // create once and reuse
+ var pos = new THREE.Vector3(); // create once and reuse
+
+ vec.set(
+ (mousePos.x / window.innerWidth) * 2 - 1,
+ - (mousePos.y / window.innerHeight) * 2 + 1,
+ 0.5);
+
+ vec.unproject(camera);
+
+ vec.sub(camera.position).normalize();
+
+ var distance = - camera.position.z / vec.z;
+
+ pos.copy(camera.position).add(vec.multiplyScalar(distance));
+ return pos;
+ }
+}
\ No newline at end of file |
