UNPKG

react-cloth

Version:

A customizable cloth simulation component for React and Next.js

249 lines (246 loc) 10.8 kB
import React, { useRef, useState, useEffect } from '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 = useRef(null); var _o = useState({ width: width, height: height }), canvasSize = _o[0], setCanvasSize = _o[1]; 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.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", } })); }; export { ClothSimulation }; //# sourceMappingURL=index.esm.js.map