react-cloth
Version:
A customizable cloth simulation component for React and Next.js
257 lines (250 loc) • 11.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var ClothSimulation = function (_a) {
var _b = _a.width, width = _b === void 0 ? window.innerWidth : _b, _c = _a.height, height = _c === void 0 ? window.innerHeight : _c, _d = _a.clothWidth, clothWidth = _d === void 0 ? Math.floor(width / 14) : _d, _e = _a.clothHeight, clothHeight = _e === void 0 ? 60 : _e, _f = _a.spacing, spacing = _f === void 0 ? 12 : _f, _g = _a.gravity, gravity = _g === void 0 ? 1200 : _g, _h = _a.mouseInfluence, mouseInfluence = _h === void 0 ? 30 : _h, _j = _a.mouseCut, mouseCut = _j === void 0 ? 15 : _j, _k = _a.tearDistance, tearDistance = _k === void 0 ? 60 : _k, _l = _a.backgroundColor, backgroundColor = _l === void 0 ? "transparent" : _l, _m = _a.lineColor, lineColor = _m === void 0 ? "#888" : _m;
var canvasRef = React.useRef(null);
var _o = React.useState({ width: width, height: height }), canvasSize = _o[0], setCanvasSize = _o[1];
React.useEffect(function () {
var canvas = canvasRef.current;
if (!canvas)
return;
var ctx = canvas.getContext("2d");
if (!ctx)
return;
var clothSettings = {
physicsAccuracy: 3,
mouseInfluence: mouseInfluence,
mouseCut: mouseCut,
gravity: gravity,
clothHeight: clothHeight,
clothWidth: clothWidth,
startY: 10,
spacing: spacing,
tearDistance: tearDistance,
};
var mouseX = 0, mouseY = 0;
var mouseDown = false;
var mouseButton = 0;
var Point = /** @class */ (function () {
function Point(x, y) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.vx = 0;
this.vy = 0;
this.pinX = null;
this.pinY = null;
this.constraints = [];
}
Point.prototype.update = function (delta) {
if (mouseDown) {
var dx = this.x - mouseX;
var dy = this.y - mouseY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (mouseButton === 0 && dist < mouseInfluence) {
this.px = this.x - (mouseX - this.x) * 0.5;
this.py = this.y - (mouseY - this.y) * 0.5;
}
else if (mouseButton === 2 && dist < mouseCut) {
this.constraints = this.constraints.filter(function (constraint) {
return Math.sqrt(Math.pow((constraint.p1.x - constraint.p2.x), 2) +
Math.pow((constraint.p1.y - constraint.p2.y), 2)) > tearDistance;
});
}
}
this.addForce(0, gravity);
delta *= delta;
var nx = this.x + (this.x - this.px) * 0.99 + (this.vx / 2) * delta;
var ny = this.y + (this.y - this.py) * 0.99 + (this.vy / 2) * delta;
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
this.vy = this.vx = 0;
};
Point.prototype.resolveConstraints = function () {
var _a, _b;
if (this.pinX !== null && this.pinY !== null) {
this.x = this.pinX;
this.y = this.pinY;
return;
}
for (var _i = 0, _c = this.constraints; _i < _c.length; _i++) {
var constraint = _c[_i];
constraint.resolve();
}
this.x = Math.max(1, Math.min(((_a = canvas === null || canvas === void 0 ? void 0 : canvas.width) !== null && _a !== void 0 ? _a : 200) - 1, this.x));
this.y = Math.max(1, Math.min(((_b = canvas === null || canvas === void 0 ? void 0 : canvas.height) !== null && _b !== void 0 ? _b : 200) - 1, this.y));
};
Point.prototype.attach = function (point) {
this.constraints.push(new Constraint(this, point));
};
Point.prototype.addForce = function (x, y) {
this.vx += x;
this.vy += y;
};
Point.prototype.pin = function (pinx, piny) {
this.pinX = pinx;
this.pinY = piny;
};
return Point;
}());
var Constraint = /** @class */ (function () {
function Constraint(p1, p2) {
this.p1 = p1;
this.p2 = p2;
this.length = spacing;
}
Constraint.prototype.resolve = function () {
var _this = this;
var dx = this.p1.x - this.p2.x;
var dy = this.p1.y - this.p2.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > tearDistance) {
this.p1.constraints = this.p1.constraints.filter(function (c) { return c !== _this; });
this.p2.constraints = this.p2.constraints.filter(function (c) { return c !== _this; });
return;
}
var diff = (this.length - dist) / dist;
var px = dx * diff * 0.5;
var py = dy * diff * 0.5;
this.p1.x += px;
this.p1.y += py;
this.p2.x -= px;
this.p2.y -= py;
};
return Constraint;
}());
var Cloth = /** @class */ (function () {
function Cloth() {
var _a;
this.points = [];
var startX = ((_a = canvas === null || canvas === void 0 ? void 0 : canvas.width) !== null && _a !== void 0 ? _a : 200) / 2 - (clothWidth * spacing) / 2;
for (var y = 0; y <= clothHeight; y++) {
for (var x = 0; x <= clothWidth; x++) {
var p = new Point(startX + x * spacing, clothSettings.startY + y * spacing);
if (x !== 0)
p.attach(this.points[this.points.length - 1]);
if (y === 0)
p.pin(p.x, p.y);
if (y !== 0)
p.attach(this.points[x + (y - 1) * (clothWidth + 1)]);
this.points.push(p);
}
}
}
Cloth.prototype.update = function () {
for (var i = 0; i < clothSettings.physicsAccuracy; i++) {
this.points.forEach(function (p) { return p.resolveConstraints(); });
}
this.points.forEach(function (p) { return p.update(0.016); });
};
Cloth.prototype.draw = function () {
if (!ctx)
return;
ctx.beginPath();
ctx.strokeStyle = lineColor;
this.points.forEach(function (p) {
p.constraints.forEach(function (c) {
ctx.moveTo(c.p1.x, c.p1.y);
ctx.lineTo(c.p2.x, c.p2.y);
});
});
ctx.stroke();
};
return Cloth;
}());
var resizeCanvas = function () {
setCanvasSize({
width: width || window.innerWidth,
height: height || window.innerHeight,
});
};
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
var cloth = new Cloth();
var handleMouseDown = function (e) {
var rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
mouseDown = true;
mouseButton = e.button;
};
var touchDown = function (e) {
// mouse.current.button = 1; // Changed from e.which to e.button
var rect = canvas.getBoundingClientRect();
mouseX = e.touches[0].clientX - rect.left;
mouseY = e.touches[0].clientY - rect.top;
mouseDown = true;
mouseButton = 2;
};
var touchUp = function () { return function (e) {
// mouse.current.button = 1; // Changed from e.which to e.button
mouseDown = false;
}; };
var handleMouseMove = function (e) {
var rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
};
var handleTouchMove = function (e) {
var rect = canvas.getBoundingClientRect();
mouseX = e.touches[0].clientX - rect.left;
mouseY = e.touches[0].clientY - rect.top;
};
var handleMouseUp = function () {
mouseDown = false;
};
var handleContextMenu = function (e) {
e.preventDefault();
};
var update = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cloth.update();
cloth.draw();
requestAnimationFrame(update);
};
window.addEventListener("touchstart", touchDown);
window.addEventListener("touchmove", handleTouchMove);
window.addEventListener("touchend", touchUp);
window.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
window.addEventListener("contextmenu", handleContextMenu);
update();
return function () {
window.removeEventListener("resize", resizeCanvas);
window.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
window.removeEventListener("contextmenu", handleContextMenu);
};
}, [
width,
height,
clothWidth,
clothHeight,
spacing,
gravity,
mouseInfluence,
mouseCut,
tearDistance,
lineColor,
]);
return (React__default["default"].createElement("canvas", { ref: canvasRef, width: canvasSize.width, height: canvasSize.height, style: {
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: backgroundColor,
zIndex: -20,
pointerEvents: "auto",
} }));
};
exports.ClothSimulation = ClothSimulation;
//# sourceMappingURL=index.js.map