Create a Three.js simulation of air flow passing through a cylindrical tube

I'm attempting to achieve the same fluid direction and airflow as demonstrated in this example:

https://i.sstatic.net/TKLto.gif

It seems that only the texture is in motion.

I am struggling to comprehend how to simulate airflow in the right direction within the pipe and on the surface like shown.

Any suggestions or ideas are greatly welcomed!

(My thought is they might be using a UV unwrap technique combined with animating the UV offset of the texture)

Answer №1

It seems that adjusting a texture's UV offset can achieve the desired effect

texture.offset.x = someAnimatedValue;
texture.offset.y = someAnimatedValue;

To create the pipe, most 3D modeling software (such as blender, maya, 3d studio max, etc...) allows you to extrude a line along a path. For the pipe, start with a circle and extrude it along the designated path. To add a wall or fence inside the pipe, extrude a line down the same curve. The default UV coordinates should be correct for scrolling.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);

const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

// create a textured arrow
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 64;
ctx.canvas.height = 64;

ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.fillRect(0, 0, 64, 64);

ctx.translate(32, 32);
ctx.rotate(Math.PI * .5);
ctx.fillStyle = "rgb(0,255,255)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "48px sans-serif";
ctx.fillText("➡︎", 0, 0);

const texture = new THREE.CanvasTexture(ctx.canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.x = 4;
texture.repeat.y = 9;

const radiusTop = 1;
const radiusBottom = 1;
const height = 5;
const radiusSegments = 20;
const heightSegments = 2;
const openEnded = true;
const geometry = new THREE.CylinderBufferGeometry( 
  radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded);
const material = new THREE.MeshBasicMaterial({
   map: texture,
   side: THREE.DoubleSide,
   depthWrite: false,
   depthTest: false,
   transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
mesh.rotation.z = Math.PI * .5;

function render(time) {
  time *= 0.001;
  
  resize();
  
  const cameraSpeed = time * 0.3;
  const cameraRadius = 5;
  camera.position.x = Math.cos(cameraSpeed) * cameraRadius;
  camera.position.y = 1;
  camera.position.z = Math.sin(cameraSpeed) * cameraRadius;
  camera.lookAt(mesh.position);

  texture.offset.y = (time * 3 % 1);
  
  renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resize() {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>

Based on the original diagram, it appears they are animating a strip within the pipe with an arrow texture that scrolls along it. After creating the pipes in your modeling software, extrude a line along the path.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);

const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

// create a textured arrow
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 64;
ctx.canvas.height = 64;

ctx.translate(32, 32);
ctx.rotate(Math.PI * .5);
ctx.fillStyle = "rgb(0,255,255)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "48px sans-serif";
ctx.fillText("➡︎", 0, 0);

const texture = new THREE.CanvasTexture(ctx.canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.x = 1;
texture.repeat.y = 5;

const radiusTop = 1;
const radiusBottom = 1;
const height = 5;
const radiusSegments = 20;
const heightSegments = 2;
const openEnded = true;
const geometry = new THREE.CylinderBufferGeometry( 
  radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded);
const material = new THREE.MeshBasicMaterial({
   color: 0x4040FF,
   opacity: 0.5,
   side: THREE.DoubleSide,
   depthWrite: false,
   depthTest: false,
   transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
mesh.rotation.z = Math.PI * .5;

const stripGeo = new THREE.PlaneBufferGeometry(radiusTop * 1.7, height);
const stripMat = new THREE.MeshBasicMaterial({
   map: texture,
   opacity: 0.5,
   side: THREE.DoubleSide,
   depthWrite: false,
   depthTest: false,
   transparent: true,
});
const stripMesh = new THREE.Mesh(stripGeo, stripMat);
scene.add(stripMesh);
stripMesh.rotation.z = Math.PI * .5;

function render(time) {
  time *= 0.001;
  
  resize();
  
  const cameraSpeed = time * 0.3;
  const cameraRadius = 5;
  camera.position.x = Math.cos(cameraSpeed) * cameraRadius;
  camera.position.y = 1;
  camera.position.z = Math.sin(cameraSpeed) * cameraRadius;
  camera.lookAt(mesh.position);

  texture.offset.y = (time * 3 % 1);
  
  renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resize() {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>

PS: Sorting issues were not addressed in this response, but handling them could be a separate query

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Verify whether the input is currently being focused on

My current issue involves an input field that requires a minimum number of characters. If a user begins typing in the field but then switches to another without meeting the character requirement, I would like to change the text color to red. I attempted u ...

Clarification: Javascript to Toggle Visibility of Divs

There was a similar question that partially solved my issue, but I'm wondering if using class or id NAMES instead of ul li - such as .menu, #menu, etc. would work in this scenario. CSS: div { display:none; background:red; width:200px; height:200px; } ...

Submitting a JSPX form to interact with PHP backend

Can a JSP/JSPX form be used to submit data to a PHP file? The PHP file will handle validation and database updates, then send a response back to the same JSP/JSPX form. The process should be done using AJAX with jQuery. What steps are involved in achievi ...

Explore the world of threejs with a sphere adorned with cube bumps

Currently, I am navigating through the internet in search of a solution for my problem. The example I came across is quite impressive, take a look: . My dilemma lies in converting these squares into CSS cubes while ensuring that they do not get cut off by ...

Send PS3 through user agent for redirection

If a user is accessing the site using a PS3, I want them to be redirected to a different webpage Below is the code snippet I have been attempting to use: <script language=javascript> <!-- if ((navigator.userAgent.match(/iMozilla/i)) || (navigato ...

Guide on using Fetch API to send a request to a PHP file using the POST method

Hey everyone, I've recently started delving into the world of PHP and I encountered an issue while attempting to send a post request from my JavaScript file to my PHP file using the Fetch API. Upon making the request in my console, I received the foll ...

Problem with Material UI Checkbox failing to switch states

I'm a bit confused about the functionality of my checkbox in Material UI. The documentation makes it seem simple, but I'm struggling to get my checkbox to toggle on or off after creating the component. const createCheckBox = (row, checkBoxStatus, ...

Dividing the array into distinct subarray groups

I am working with a JavaScript array that contains strings, like this: let a = ["a", "a", "a", "b", "c", "c", "b", "b", "b", "d", "d", "e&quo ...

Setting up Django model form using jQuery and JavaScript

In my project, I am working with three different models: class Instances(models.Model): name_of_instances = models.CharField(max_length=255) url_of_instances=models.URLField(max_length=255,default='') def __str__(sel ...

Utilizing express.js alongside the native MongoDB driver: A comprehensive guide

I'm facing difficulties when attempting a simple mongoDB query from my express app: app.js var express = require('express'); var routes = require('./routes'); var user = require('./routes/user'); var http = re ...

extracting the ID from within an iframe

Currently, I am developing a script using pure javascript. parent.*nameofiframe*.document.getElementById('error').value; However, when attempting to achieve the same using jQuery, it doesn't seem to work: $('*nameofiframe*', win ...

Use JavaScript to convert only the initial letter to uppercase

Once again, I am in the process of learning! Apologies for the simple questions, but learning is key... Attempting to implement a trick I found online to change all letters to uppercase, I am now trying to adjust it to only capitalize the first letters. T ...

assert.deepPartiallyEqual method designed for comparing two objects in an asymmetric manner

I've been struggling to come up with suitable names for this function, but let me explain what I'm looking for: I need an assertion function similar to assert.deepEqual(obj1, obj2), but instead of recursively comparing obj1 with obj2, it should ...

Adjust the form action and text input name according to the selected radio input

Seeking assistance with the following code, can someone help? $(document).ready(function() { $('#searchform').submit(function() { var action = ''; if($('.action_url').val() == 'l_catalog') { ...

Using the jasmine framework to create conditional test cases

Currently, I am working on Jasmine and my goal is to ensure that the test cases run only when the site's response status is okay (200). If the site fails to load, I do not want the test cases to execute. To achieve this, I am checking the site's ...

Submitting option values in AngularJS: A step-by-step guide

Why does AngularJS ng-options use label for value instead of just the value itself? Here is my current code: <select ng-model="gameDay" ng-options="gameDay for gameDay in gameDayOptions"> This currently displays: <select ng-model="gameDay" ng- ...

This module is not compatible with the "node" engine when trying to install React-chartjs-2 Chart.js

While working on a new project, I successfully implemented a doughnut chart using chart.js and its react wrapper. However, when attempting to install it in the main project, I encountered some difficulties. Here is an example of the new project where the ...

Leverage the power of regular expressions in JavaScript for organizing and handling source files

Embarking on my coding journey with JavaScript, I have also been exploring the world of Three.js, a webgl library. After watching tutorials and conducting experiments, I am proud to share my latest creation: . In my code, you'll notice that the obje ...

The paragraph tag remains unchanged

I am currently working on developing a feature that saves high scores using local storage. The project I'm working on is a quiz application which displays the top 5 scores at the end, regardless of whether the quiz was completed or not. However, I&apo ...

Learn how to effortlessly move a file into a drag-and-drop area on a web page with Playwright

I am currently working with a drag-zone input element to upload files, and I am seeking a way to replicate this action using Playwright and TypeScript. I have the requirement to upload a variety of file types including txt, json, png. https://i.stack.img ...