Ткань имитация 3D-модели ткани.
Я видел много симуляций ткани в three.js . Я обнаружил, что это делается только с плоскими двумерными поверхностями. Но есть ли способ смоделировать трехмерную модель ткани, подобную показанной ниже?
Есть много руководств по моделированию 2D плоскости, например
И код для них приведен ниже...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<script src="../build/three.js"></script>
<script src="../src/OrbitControls.js"></script>
<script>
var params = {
enableWind: true,
tooglePins: togglePins
};
var DAMPING = 0.03;
var DRAG = 1 - DAMPING;
var MASS = 0.1;
var restDistance = 25;
var xSegs = 10;
var ySegs = 10;
var clothFunction = plane(restDistance * xSegs, restDistance * ySegs);
var cloth = new Cloth(xSegs, ySegs);
var GRAVITY = 981 * 1.4;
var gravity = new THREE.Vector3(0, -GRAVITY, 0).multiplyScalar(MASS);
var TIMESTEP = 18 / 1000;
var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
var pins = [];
var windForce = new THREE.Vector3(0, 0, 0);
var tmpForce = new THREE.Vector3();
var lastTime;
function plane(width, height) {
return function(u, v, target) {
var x = (u - 0.5) * width;
var y = (v + 0.5) * height;
var z = 0;
target.set(x, y, z);
};
}
function Particle(x, y, z, mass) {
this.position = new THREE.Vector3();
this.previous = new THREE.Vector3();
this.original = new THREE.Vector3();
this.a = new THREE.Vector3(0, 0, 0); // acceleration
this.mass = mass;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
// init
clothFunction(x, y, this.position); // position
clothFunction(x, y, this.previous); // previous
clothFunction(x, y, this.original);
}
// Force -> Acceleration
Particle.prototype.addForce = function(force) {
this.a.add(
this.tmp2.copy(force).multiplyScalar(this.invMass)
);
};
// Performs Verlet integration
Particle.prototype.integrate = function(timesq) {
var newPos = this.tmp.subVectors(this.position, this.previous);
newPos.multiplyScalar(DRAG).add(this.position);
newPos.add(this.a.multiplyScalar(timesq));
this.tmp = this.previous;
this.previous = this.position;
this.position = newPos;
this.a.set(0, 0, 0);
};
var diff = new THREE.Vector3();
function satisfyConstraints(p1, p2, distance) {
diff.subVectors(p2.position, p1.position);
var currentDist = diff.length();
if (currentDist === 0) return; // prevents division by 0
var correction = diff.multiplyScalar(1 - distance / currentDist);
var correctionHalf = correction.multiplyScalar(0.5);
p1.position.add(correctionHalf);
p2.position.sub(correctionHalf);
}
function Cloth(w, h) {
w = w || 10;
h = h || 10;
this.w = w;
this.h = h;
var particles = [];
var constraints = [];
var u, v;
// Create particles
for (v = 0; v <= h; v++) {
for (u = 0; u <= w; u++) {
particles.push(
new Particle(u / w, v / h, 0, MASS)
);
}
}
// Structural
for (v = 0; v < h; v++) {
for (u = 0; u < w; u++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
}
for (u = w, v = 0; v < h; v++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
}
for (v = h, u = 0; u < w; u++) {
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
this.particles = particles;
this.constraints = constraints;
function index(u, v) {
return u + v * (w + 1);
}
this.index = index;
}
function simulate(time) {
if (!lastTime) {
lastTime = time;
return;
}
var i, j, il, particles, particle, constraints, constraint;
// Aerodynamics forces
if (params.enableWind) {
var indx;
var normal = new THREE.Vector3();
var indices = clothGeometry.index;
var normals = clothGeometry.attributes.normal;
particles = cloth.particles;
for (i = 0, il = indices.count; i < il; i += 3) {
for (j = 0; j < 3; j++) {
indx = indices.getX(i + j);
normal.fromBufferAttribute(normals, indx);
tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
particles[indx].addForce(tmpForce);
}
}
}
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
particle.addForce(gravity);
particle.integrate(TIMESTEP_SQ);
}
// Start Constraints
constraints = cloth.constraints;
il = constraints.length;
for (i = 0; i < il; i++) {
constraint = constraints[i];
satisfyConstraints(constraint[0], constraint[1], constraint[2]);
}
// Floor Constraints
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
pos = particle.position;
if (pos.y < -250) {
pos.y = -250;
}
}
// Pin Constraints
for (i = 0, il = pins.length; i < il; i++) {
var xy = pins[i];
var p = particles[xy];
p.position.copy(p.original);
p.previous.copy(p.original);
}
}
/* testing cloth simulation */
var pinsFormation = [];
var pins = [6];
pinsFormation.push(pins);
pins = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
pinsFormation.push(pins);
pins = [0];
pinsFormation.push(pins);
pins = []; // cut the rope ;)
pinsFormation.push(pins);
pins = [0, cloth.w]; // classic 2 pins
pinsFormation.push(pins);
pins = pinsFormation[1];
function togglePins() {
pins = pinsFormation[~~(Math.random() * pinsFormation.length)];
}
var container, stats;
var camera, scene, renderer;
var clothGeometry;
var sphere;
var object;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0xcce0ff, 500, 10000);
// camera
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(1000, 50, 1500);
// lights
scene.add(new THREE.AmbientLight(0x666666));
var light = new THREE.DirectionalLight(0xdfebff, 1);
light.position.set(50, 200, 100);
light.position.multiplyScalar(1.3);
light.castShadow = true;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
var d = 300;
light.shadow.camera.left = -d;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = -d;
light.shadow.camera.far = 1000;
scene.add(light);
// cloth material
var loader = new THREE.TextureLoader();
var clothTexture = loader.load('textures/water/Water_1_M_Flow.jpg');
clothTexture.anisotropy = 16;
var clothMaterial = new THREE.MeshLambertMaterial({
map: clothTexture,
side: THREE.DoubleSide,
// wireframe: true,
// alphaTest: 0.5
});
// cloth geometry
clothGeometry = new THREE.ParametricBufferGeometry(clothFunction, cloth.w, cloth.h);
// cloth mesh
object = new THREE.Mesh(clothGeometry, clothMaterial);
object.position.set(0, 0, 0);
object.castShadow = true;
scene.add(object);
// object.customDepthMaterial = new THREE.MeshDepthMaterial({
// depthPacking: THREE.RGBADepthPacking,
// map: clothTexture,
// alphaTest: 0.5
// });
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
// controls
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI * 0.5;
controls.minDistance = 1000;
controls.maxDistance = 5000;
window.addEventListener('resize', onWindowResize, false);
}
//
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
var time = Date.now();
var windStrength = Math.cos(time / 7000) * 20 + 40;
windForce.set(Math.sin(time / 2000), Math.cos(time / 3000), Math.sin(time / 1000));
windForce.normalize();
windForce.multiplyScalar(windStrength);
simulate(time);
render();
}
function render() {
var p = cloth.particles;
for (var i = 0, il = p.length; i < il; i++) {
var v = p[i].position;
clothGeometry.attributes.position.setXYZ(i, v.x, v.y, v.z);
}
clothGeometry.attributes.position.needsUpdate = true;
clothGeometry.computeVertexNormals();
renderer.render(scene, camera);
}
</script>
</body>
</html>
Могу ли я сделать сетку. Точно так же, как повесить ткань, и когда дует ветер, они должны реагировать соответствующим образом. Будь то использование three.js вместе с ammo.js или также cannon.js
2 ответа
Код, который вы разместили, не будет касаться одежды, так как нет столкновений. Код в ammo.js будет, но вам нужно сгенерировать одежду самостоятельно.
Ткань обычно моделируется с помощью масс и пружин.
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
выше - диаграмма масс (M) и пружин (s). Каждая пружина соединена между двумя массами и пытается удерживать массы от слишком большого растяжения и сближения. Вам нужны тысячи масс и пружин для имитации одежды.
Причина, по которой демо-версии находятся в самолетах, заключается в том, что это самая простая демо-версия. Если вам нужна одежда, вам нужно пройти по многоугольникам вашей одежды, а затем создать массы и пружины. Кроме того, вам необходимо связать массы с соответствующими вершинами в модели одежды, чтобы после запуска моделирования вы могли применить новые положения масс обратно к вершинам одежды.
Вдобавок вам нужны столкновения на теле персонажа, на которого вы собираетесь надеть одежду, с которой будут сталкиваться массы, чтобы они не попадали внутрь тела и просто падали на пол. Большинство физических движков имеют несколько оптимизированных примитивов, таких как коробка, сфера, капсула, цилиндр. Они также могут использовать универсальные полигоны для столкновения, но они медленнее, поэтому вам решать, можете ли вы использовать несколько примитивных форм, прикрепленных к вашей модели, для столкновения, или вам нужна более высокая точность использования полигонов для ваших столкновений..
В любом случае, чем больше масс вы добавляете на область ткани, тем лучше ткань будет выглядеть, но тем медленнее она будет работать, поэтому вам нужно решить, где находится компромисс между хорошим внешним видом и быстрым бегом.
ammo.js AFAICT недокументирован, за исключением того, что это порт Bullet Physics, документация которого находится здесь
Я не вижу демонстраций JavaScript для нестандартной ткани.
Эта демонстрация ammo.js, похоже, не подходит к одежде, потому что, если бы показанные фигуры на самом деле были одеждой, они бы просто свернулись в кучу, а не действовали бы так, как будто они надуты, но тогда, возможно, это поведение является настройкой. Вам нужно будет покопаться в документации и / или в этом образце.
Вам нужно будет отделить геометрию вашей одежды от модели человеческого тела / манекена, превратить одежду в мягкое тело или вручную создать массы и пружины, а затем также сделать твердое тело человека / манекена либо из сетки, либо из примитивов, чтобы оно держалось. одежду.
Если бы я делал это, я бы начал с куба твердого тела внутри сферы мягкого тела и посмотрел, насколько детально мне нужно сделать сферу, чтобы она вела себя как одежда (складывание и складывание).
Popup Dev Я работал с этим, но обнаружил, что использование геометрии плоскости для манипулирования намного проще. У меня также есть вопросы о том, как в исходном примере ткани вычисляется и используется "ветер". Свяжитесь со мной в твиттере, и давайте обсудим.