変位勾配テンソルによる変形(ツイスト)
前回は変位勾配テンソルの成分に定数を与えました。テンソルの成分はもちろん位置によって変化する変数でもよいので、今回はz軸周りの回転量をz値によって変化させることで、ツイストを作ってみるね。もともとz軸周りの回転行列は
$$
R = \left( \begin{matrix} \cos\theta & \sin\theta & 0 \cr -\sin\theta & \cos\theta & 0 \cr 0 & 0 & 0 \end{matrix} \right)
$$
となるので、この$$\theta=\alpha z$$のように与えることでz軸周りのツイストを実現できるね。$${\alpha}$$がツイストの単位長さあたりの回転量(ラジアン)だね。下図が結果だよ。今回もthree.jsを用いて作ってみました。
ちなみにこのツイストに対応する変形勾配テンソルは先の回転行列をテーラー展開して2次以降を無視しておいて、対角成分から1を引いた値、
$$
D = \left( \begin{matrix} 0 & \alpha z & 0 \cr -\alpha z & 0 & 0 \cr 0 & 0 & 0 \end{matrix} \right)
$$
となるね。変位勾配テンソルの成分には連続体としての整合性を保証するための条件「歪の適合方程式」
$$
\frac{\partial ^2 E_{xx}}{\partial y \partial z} = \frac{\partial}{\partial x} \left(-\frac{\partial E_{yz}}{\partial x} + \frac{\partial E_{zx}}{\partial y} +\frac{\partial E_{xy}}{\partial z} \right)
$$
$$
\frac{\partial ^2 E_{yy}}{\partial z \partial x} = \frac{\partial}{\partial y} \left(-\frac{\partial E_{zx}}{\partial y} + \frac{\partial E_{xy}}{\partial z} +\frac{\partial E_{yz}}{\partial x} \right)
$$
$$
\frac{\partial ^2 E_{zz}}{\partial x \partial y} = \frac{\partial}{\partial z} \left(-\frac{\partial E_{xy}}{\partial z} + \frac{\partial E_{yz}}{\partial x} +\frac{\partial E_{zx}}{\partial y} \right)
$$
$$
2\,\frac{\partial ^2 E_{xy}}{\partial x \partial y} =\frac{\partial^2 E_{xx}}{\partial y^2} +\frac{\partial^2 E_{yy}}{\partial x^2}
$$
$$
2\,\frac{\partial ^2 E_{yz}}{\partial y \partial z} =\frac{\partial^2 E_{yy}}{\partial z^2} +\frac{\partial^2 E_{zz}}{\partial y^2}
$$
$$
2\,\frac{\partial ^2 E_{zx}}{\partial z \partial x} =\frac{\partial^2 E_{zz}}{\partial x^2} +\frac{\partial^2 E_{xx}}{\partial z^2}
$$
を満たす必要があるね。$${E_{ij}}$$は歪テンソルの成分なのだけれども、ツイストはそもそも歪成分を持たないので、どれだけツイストしても問題ないってことだね。
Javascriptプログラムソース
Javascriptプログラムソースを用意したよ。もし良かったら試してみてください。これからも応援よろしくお願いしまーす!
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>変位勾配テンソルによる変形(変数:ツイスト)</title>
<script src="https://natural-science.or.jp/files/physics/UV_Grid_Sm.js"></script>
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<style>
*{padding:0px; margin:0px}
div#canvas-frame{
width: 1200px; /* 横幅 */
height: 900px; /* 縦幅 */
overflow:hidden;
}
table.matrix input{
width: 30px;
}
section#field{
padding: 10px 20px;
border: 1px solid gray;
border-radius: 10px;
margin: 10px 10px;
width: 1140px;
}
</style>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.168.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.168.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
//初期変位勾配テンソル
let D = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
let theta_max = 4.0 * Math.PI;
let Lz = 2;
function setD( r, theta, D ){
let x = r[0], y = r[1], z = r[2];
let _theta = theta * z / Lz;
D[0][0] = Math.cos( _theta ) - 1.0;
D[0][1] = Math.sin( _theta );
D[0][2] = 0;
D[1][0] = - Math.sin( _theta );
D[1][1] = Math.cos( _theta ) - 1.0;
D[1][2] = 0;
D[2][0] = 0.0;
D[2][1] = 0.0;
D[2][2] = 0.0;
}
//変位勾配テンソルによる変位計算
function Deformation( D, r ){
let x = D[ 0 ][ 0 ] * r[ 0 ] + D[ 0 ][ 1 ] * r[ 1 ] + D[ 0 ][ 2 ] * r[ 2 ];
let y = D[ 1 ][ 0 ] * r[ 0 ] + D[ 1 ][ 1 ] * r[ 1 ] + D[ 1 ][ 2 ] * r[ 2 ];
let z = D[ 2 ][ 0 ] * r[ 0 ] + D[ 2 ][ 1 ] * r[ 1 ] + D[ 2 ][ 2 ] * r[ 2 ];
return [ x, y, z ];
}
//格子点座標の初期値
let initialPositionArray = [ ]
////////////////////////////////////////////////////////////////////
// windowイベントの定義
////////////////////////////////////////////////////////////////////
window.addEventListener("load", function () {
threeStart(); //Three.jsのスタート関数の実行
});
////////////////////////////////////////////////////////////////////
// Three.jsスタート関数の定義
////////////////////////////////////////////////////////////////////
function threeStart() {
initThree(); //Three.js初期化関数の実行
initLight(); //光源初期化関数の実行
initObject(); //オブジェクト初期化関数の実行
initCamera(); //カメラ初期化関数の実行
loop(); //無限ループ関数の実行
}
////////////////////////////////////////////////////////////////////
// Three.js初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let renderer, //レンダラーオブジェクト
scene, //シーンオブジェクト
canvasFrame; //キャンバスフレームのDOM要素
function initThree() {
//キャンバスフレームDOM要素の取得
canvasFrame = document.getElementById('canvas-frame');
//レンダラーオブジェクトの生成
renderer = new THREE.WebGLRenderer({ antialias: true });
if (!renderer) alert('Three.js の初期化に失敗しました');
//レンダラーのサイズの設定
renderer.setSize(canvasFrame.clientWidth, canvasFrame.clientHeight);
//キャンバスフレームDOM要素にcanvas要素を追加
canvasFrame.appendChild(renderer.domElement);
//レンダラークリアーカラーの設定
renderer.setClearColor(0xEEEEEE, 1.0);
//シーンオブジェクトの生成
scene = new THREE.Scene();
}
////////////////////////////////////////////////////////////////////
// カメラ初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let camera; //カメラオブジェクト
let trackball;
function initCamera() {
//カメラオブジェクトの生成
camera = new THREE.PerspectiveCamera(45, canvasFrame.clientWidth / canvasFrame.clientHeight, 1, 10000);
//カメラの位置の設定
camera.position.set(2.2, 2.2, 1.2);
//カメラの上ベクトルの設定
camera.up.set(0, 0, 1);
//カメラの中心位置ベクトルの設定
camera.lookAt(new THREE.Vector3(0,0,0)); //トラックボール利用時は自動的に無効
//トラックボールオブジェクトの宣言
trackball = new TrackballControls(camera, canvasFrame);
//トラックボール動作範囲のサイズとオフセットの設定
trackball.screen.width = canvasFrame.clientWidth; //横幅
trackball.screen.height = canvasFrame.clientHeight; //縦幅
trackball.screen.offsetLeft = canvasFrame.getBoundingClientRect().left; //左オフセット
trackball.screen.offsetTop = canvasFrame.getBoundingClientRect().top; //右オフセット
//トラックボールの回転無効化と回転速度の設定
trackball.noRotate = false;
trackball.rotateSpeed = 2.0;
//トラックボールの拡大無効化と拡大速度の設定
trackball.noZoom = false;
trackball.zoomSpeed = 4.0;
//トラックボールのカメラ中心移動の無効化と中心速度の設定
trackball.noPan = false;
trackball.panSpeed = 1.0;
trackball.target = new THREE.Vector3(0, 0, 0);
//トラックボールのスタティックムーブの有効化
trackball.staticMoving = true;
//トラックボールのダイナミックムーブ時の減衰定数
trackball.dynamicDampingFactor = 0.3;
}
////////////////////////////////////////////////////////////////////
// 光源初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let directionalLight, //平行光源オブジェクト
ambientLight; //環境光オブジェクト
function initLight() {
//平行光源オブジェクトの生成
directionalLight = new THREE.DirectionalLight(0xffffff, 2.0, 0);
//平行光源オブジェクトの位置の設定
directionalLight.position.set(50, 50, 50);
//平行光源オブジェクトの影の生成元
directionalLight.castShadow = false;
//平行光源オブジェクトのシーンへの追加
scene.add(directionalLight);
//環境光オブジェクトの生成
ambientLight = new THREE.AmbientLight(0x666666);
//環境光オブジェクトのシーンへの追加
scene.add(ambientLight);
}
////////////////////////////////////////////////////////////////////
// オブジェクト初期化関数の定義
////////////////////////////////////////////////////////////////////
let box;
function initObject() {
//スカイドームの生成
let skydome = createSkydome();
//床オブジェクトをシーンへ追加
scene.add( skydome );
//床オブジェクトの生成
let floor = createFloor();
//床オブジェクトをシーンへ追加
//scene.add( floor );
//img要素の生成
let img = new Image();
img.src = UV_Grid_Sm; //DataURL
let texture = new THREE.Texture( img );
texture.needsUpdate = true;
//カラースペースの指定
texture.colorSpace = THREE.SRGBColorSpace;
let L = 1;
//形状オブジェクトの宣言と生成
let geometry = new THREE.BoxGeometry(
L, //width:x軸方向の幅(デフォルト:1.0)
L, //height:y軸方向の幅(デフォルト:1.0)
Lz, //depth:z軸方向の幅(デフォルト:1.0)
100, //widthSegments:x軸方向の分割数(デフォルト:1)
100, //heightSegments:y軸方向の分割数(デフォルト:1)
100 //depthSegments:z軸方向の分割数(デフォルト:1)
);
//材質オブジェクトの宣言と生成
let material = new THREE.MeshPhongMaterial({ map: texture, color: 0xffffff, specular: 0xffffff, shininess: 200 })
//直方体オブジェクトの生成
box = new THREE.Mesh(geometry, material);
//直方体オブジェクトのシーンへの追加
scene.add(box);
//直方体オブジェクトの位置座標を設定
box.position.set(-L/2, -L/2, -L*1.2);
for(let i = 0; i < box.geometry.attributes.position.array.length/3; i++){
box.geometry.attributes.position.array[ 3 * i + 0 ] += 0;
box.geometry.attributes.position.array[ 3 * i + 1 ] += 0;
box.geometry.attributes.position.array[ 3 * i + 2 ] += Lz/2;
}
for(let i = 0; i < box.geometry.attributes.position.array.length; i++){
initialPositionArray.push( box.geometry.attributes.position.array[i] );
}
let arrowY = new Arrow3D( THREE, 0.1, 0.2, 0.02, 0x00ff00 );
arrowY.setArrowBottomToTop( {x:-L/2, y:-L/2, z:-L*1.2}, {x:-L/2, y:1.2*L, z:-L*1.2} )
scene.add( arrowY.CG );
let arrowX = new Arrow3D( THREE, 0.1, 0.2, 0.02, 0xff0000 );
arrowX.setArrowBottomToTop( {x:-L/2, y:-L/2, z:-L*1.2}, {x:1.2*L, y:-L/2, z:-L*1.2} )
scene.add( arrowX.CG );
let arrowZ = new Arrow3D( THREE, 0.1, 0.2, 0.02, 0x0000ff );
arrowZ.setArrowBottomToTop( {x:-L/2, y:-L/2, z:-L*1.2}, {x:-L/2, y:-L/2, z:1.2*L} )
scene.add( arrowZ.CG );
}
////////////////////////////////////////////////////////////////////
// 無限ループ関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let step = 0; //ステップ数
function loop() {
//トラックボールによるカメラオブジェクトのプロパティの更新
trackball.update();
let theta = theta_max * (step%2000) / 2000;
for(let i = 0; i < box.geometry.attributes.position.array.length/3; i++){
let x = initialPositionArray[ 3 * i + 0 ];
let y = initialPositionArray[ 3 * i + 1 ];
let z = initialPositionArray[ 3 * i + 2 ];
let dx = [x, y, z];
setD( dx, theta, D )
let dr = Deformation( D, dx );
box.geometry.attributes.position.array[ 3 * i + 0 ] = dx[ 0 ] + dr[ 0 ];
box.geometry.attributes.position.array[ 3 * i + 1 ] = dx[ 1 ] + dr[ 1 ];
box.geometry.attributes.position.array[ 3 * i + 2 ] = dx[ 2 ] + dr[ 2 ];
}
box.geometry.attributes.position.needsUpdate = true;
box.geometry.computeVertexNormals();
step ++;
//レンダリング
renderer.render(scene, camera);
//「loop()」関数の呼び出し
requestAnimationFrame(loop);
}
//床オブジェクト生成関数
function createFloor(){
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load( generateFloorTextureDataURL( [0xFFFFFF, 0x555555] ) )
//テクスチャラッピングの指定
texture.wrapS = THREE.RepeatWrapping; //x軸方向
texture.wrapT = THREE.RepeatWrapping; //y軸方向
//リピートの指定
texture.repeat.set(10, 10);
//カラースペースの指定
texture.colorSpace = THREE.SRGBColorSpace;
texture.mapWrapT = "RepeatWrapping";
texture.mapWrapS = "RepeatWrapping";
//異方性フィルダリング指数の指定
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
//形状オブジェクトの宣言と生成
let geometry = new THREE.PlaneGeometry(5, 5); //平面オブジェクト
//材質オブジェクトの宣言と生成
let material = new THREE.MeshBasicMaterial({
color: 0xffffff,
map: texture,
side: THREE.DoubleSide
});
//平面オブジェクトの生成
return new THREE.Mesh(geometry, material);;
}
//スカイドーム生成関数
function createSkydome(){
let vertexShader = "//バーテックスシェーダー\n" +
"//頂点シェーダーからフラグメントシェーダーへの転送する変数\n" +
"varying vec3 vWorldPosition;\n" +
"void main( ) {\n" +
" //ワールド座標系における頂点座標\n" +
" vec4 worldPosition = modelMatrix * vec4( position, 1.0 );\n" +
" vWorldPosition = worldPosition.xyz;\n" +
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n" +
"}\n";
let fragmentShader = "//フラグメントシェーダ―\n" +
"//カスタムuniform変数の取得\n" +
"uniform vec3 topColor; //ドーム頂点色\n" +
"uniform vec3 bottomColor; //ドーム底辺色\n" +
"uniform float exp; //減衰指数\n" +
"uniform float offset; //高さ基準点\n" +
"//バーテックスシェーダーから転送された変数\n" +
"varying vec3 vWorldPosition;\n" +
"void main( ) {\n" +
" //高さの取得\n" +
" float h = normalize( vWorldPosition + vec3(0, 0, offset) ).z;\n" +
" if( h < 0.0) h = 0.0;\n" +
" gl_FragColor = vec4( mix( bottomColor, topColor, pow(h, exp) ), 1.0 );\n" +
"}\n";
//形状オブジェクトの宣言と生成
let geometry = new THREE.SphereGeometry( 500, 100, 100);
let uniforms = {
topColor: { type: "c", value: new THREE.Color(0.0, 0.3, 1.0) },
bottomColor: { type: "c", value: new THREE.Color(1.0, 1.0, 1.0)},
exp:{ type: "f", value : 0.8},
offset:{ type: "f", value : 400}
};
//材質オブジェクトの宣言と生成
let material = new THREE.ShaderMaterial( {
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms : uniforms,
side: THREE.BackSide,
} );
return new THREE.Mesh( geometry, material);
}
//////////////////////////////////////////////
// テクスチャマッピング用 dataURL生成関数
//////////////////////////////////////////////
function generateFloorTextureDataURL( tileColors ){
//canvas要素の生成
let canvas = document.createElement('canvas');
//canvas要素のサイズ
canvas.width = 256; //横幅
canvas.height = 256; //縦幅
//コンテキストの取得
let context = canvas.getContext('2d');
let n = 2;
let colors = [];
colors[0] = "#" + tileColors[0].toString(16);
colors[1] = "#" + tileColors[1].toString(16);
colors[0].replace("0x" , "");
colors[1].replace("0x" , "");
for ( let i = 0; i < n; i++ ) {
for ( let j = 0; j < n; j++ ) {
context.beginPath();
context.rect( i*canvas.width/n, j*canvas.height/n, canvas.width/n, canvas.height/n );
context.closePath();
let m = Math.abs( i + j ) % colors.length;
//塗りの設定
context.fillStyle = colors[m]; //塗りつぶし色の指定
context.fill(); //塗りつぶしの実行
}
}
let dataUrl = canvas.toDataURL("image/png");
return dataUrl;
}
class Arrow3D{
constructor( THREE, headWidth, headLength, bodyWidth , color ) {
this.THREE = THREE;
this.headWidth = headWidth;
this.headLength = headLength;
this.bodyWidth = bodyWidth;
this.color = color;
this.startPoint = 2 //1:中心、2:矢印の元
//円柱オブジェクトの生成
this.arrowHead = new this.THREE.Mesh(
new this.THREE.CylinderGeometry(0, headWidth, headLength, 50, 50),
new this.THREE.MeshPhongMaterial({ color: color, specular: 0xffffff, shininess: 250 })
//new this.THREE.MeshBasicMaterial({ color: color })
);
this.arrowHead.position.set( 0, - headLength/2, 0 );
//円柱オブジェクトの生成
this.arrowBody = new this.THREE.Mesh(
new this.THREE.CylinderGeometry(bodyWidth, bodyWidth, 1, 50, 50),
new this.THREE.MeshPhongMaterial({ color: color, specular: 0xffffff, shininess: 250 })
//new this.THREE.MeshBasicMaterial({ color: color })
);
this.arrowBody.position.set( 0, -headLength/2, 0 );
this.arrowBody.scale.set( 1, headLength, 1 );
this.CG = new this.THREE.Object3D( );
this.CG.arrowHead = this.arrowHead;
this.CG.arrowBody = this.arrowBody;
this.CG.add( this.arrowHead );
this.CG.add( this.arrowBody );
//初期姿勢
this.setArrowBottomToTop( {x:0, y:0, z:0}, {x:0, y:10, z:0} )
}
setArrowBottomToTop( bottom, top ){
//矢印の起点ベクトル
let L = new this.THREE.Vector3( ).subVectors( top, bottom );
//中心座標ベクトル
let R = new this.THREE.Vector3( ).addVectors( top, bottom ).multiplyScalar( 1/2 );
this.arrowBody.scale.set( 1, L.length() - this.headLength , 1 );
//3次元オブジェクトの基準位置を考慮して配置
if( this.startPoint == 1 ){
this.arrowBody.position.set( 0,- this.headLength/2, 0 );
this.arrowHead.position.set( 0, L.length()/2 - this.headLength/2, 0 );
this.CG.position.copy( R );
} else if( this.startPoint == 2 ){
this.arrowBody.position.set( 0, L.length()/2 - this.headLength/2, 0 );
this.arrowHead.position.set( 0, L.length() - this.headLength/2, 0 );
this.CG.position.copy( bottom );
}
//基準となるベクトル
let V1 = new this.THREE.Vector3(0, 1, 0);
//規格化した位置ベクトル
let V2 = L.clone().normalize();
//回転軸ベクトル
let V3 = new this.THREE.Vector3().crossVectors( V1, V2);
//回転角のcos値
let cosTheta = V1.x * V2.x + V1.y * V2.y + V1.z * V2.z;
//回転ありの場合
if( Math.abs(V3.length()) > 0.001 ){
//回転角
let theta = Math.acos( cosTheta );
//回転に対応するクオータニオンの生成
let q = new this.THREE.Quaternion().setFromAxisAngle(
V3.normalize(), theta
)
//姿勢クオータニオンに設定
this.CG.quaternion.copy( q );
}
}
setVisible( visible ){
this.arrowHead.visible = visible;
this.arrowBody.visible = visible;
}
}
</script>
</head>
<body>
<section id="field">
<div style="font-size: 20pt; padding: 10px 10px 0px 10px; float: left;">
\( \boldsymbol{r} = \left( \matrix{ x(\cos\theta-1) + y\sin\theta \cr -x\sin\theta + y(\cos\theta - 1) \cr 0 } \right)\ \to \ D = \frac{\partial \boldsymbol{r}}{\partial \boldsymbol{x}} = \left( \matrix{ \cos\theta - 1 & \sin\theta & 0 \cr -\sin\theta & \cos\theta -1 & 0 \cr 0 & 0 & 0 } \right) \)
</div>
<br style="clear:both;">
</section>
<div id="canvas-frame"></div><!-- canvas要素を配置するdiv要素 -->
</body>
</html>