見出し画像

nurbsライブラリを使ってclosedな曲線で描かれる図形をつくってみよう

こんにちわ。nap5です。


nurbsライブラリを使ってclosedな(ないしはループした)曲線で描かれる図形をつくってみたので、紹介したいと思います。


以下の記事と内容は関連しています。


前回実現したい形をハンディに達成できるライブラリが今回の内容になります。


nurbsライブラリは以下になります。


今回のプログラムになります。

実行結果で出力されるsvgパスコマンドに関してはsvg-path-editorに張り付けて確認しています。

import SVGPathCommander from "svg-path-commander";
import { samples } from "culori";
import { interpolate } from "popmotion";
import { default as chance } from "chance";
import nurbs from "nurbs";
import linspace from "linspace";

const makePath = ({ points }) => {
  let path = `M ${points[0][0]},${points[0][1]}`;
  for (let i = 1; i < points.length; i++) {
    path = path + ` L ${points[i][0]},${points[i][1]}`;
  }
  path = path + ` Z`;
  return path;
};

const buildPoints = ({ edgeCount }) => {
  const stepSize = (Math.PI * 2) / edgeCount;
  const resultList = [];
  [...Array(edgeCount)].forEach((_, edgeIndex) => {
    resultList.push([
      Math.cos(edgeIndex * stepSize),
      Math.sin(edgeIndex * stepSize),
    ]);
  });
  return resultList;
};

const curveToTrace = (c) => {
  const pointList = [];
  const splinePointList = [];
  const xy = [];
  const {
    points,
    domain,
    size: [niceSize],
  } = c;
  for (let i = 0; i < niceSize; i++) {
    pointList.push([points[i][0], points[i][1]]);
  }
  const min = domain[0][0];
  const max = domain[0][1];
  const tList = samples(100).map((t) => {
    return interpolate([0, 1], [min, max])(t);
  });
  tList.forEach((t, i) => {
    c.evaluate(xy, t);
    splinePointList.push([xy[0], xy[1]]);
  });
  return { pointList, splinePointList, tList };
};

const createKnots = ({ points, degree = 0 }) => {
  // uniformly spaced knot vector
  const knots = [];

  for (let index = 0; index < degree; index++) {
    knots.unshift(0);
  }

  const segment = points.length - (degree + 1);

  knots.push(...linspace(0, 1, segment + 2));

  for (let index = 0; index < degree; index++) {
    knots.push(1);
  }

  return knots;
};

const degree = 2;
const rotate = 30;
const edgeCount = 3;

const points = buildPoints({ edgeCount });
const knots = createKnots({ points, degree });
const weights = points.map((p) => {
  return chance().integer({ min: 1, max: 10 });
});

const curve = nurbs({
  points,
  degree,
  boundary: "closed",
  // knots,
  weights: [5, 5, 5],
  // weights,
});

const { pointList, splinePointList, tList } = curveToTrace(curve);
const originalPath = makePath({ points: pointList });
const a = new SVGPathCommander(originalPath).transform({ rotate }).toString();
console.log(a);
const splinePath = makePath({ points: splinePointList });
const b = new SVGPathCommander(splinePath).transform({ rotate }).toString();
console.log(b);


辺の数が3つ、次数が2個、時計回り30度回転の場合の実行結果は以下になります。重みの個数は座標数にシンクロしています。ここでは一旦オール5で指定しています。重みの値は1から10ぐらいの間で散らすのがいい感じになりそうです。knotsに関しては指定しないほうが、今回実現したい形に近づきます。

$ time node index.js
M0.8995 0.375L-0.8325 0.375L0.0335 -1.125Z
M-0.4247 -0.2812L-0.3984 -0.3252L-0.3722 -0.3666L-0.3459 -0.4051L-0.3197 -0.4409L-0.2934 -0.474L-0.2672 -0.5043L-0.241 -0.5319L-0.2147 -0.5566L-0.1885 -0.5787L-0.1622 -0.598L-0.136 -0.6145L-0.1097 -0.6283L-0.0835 -0.6393L-0.0573 -0.6476L-0.031 -0.6531L-0.0048 -0.6558H0.0215L0.0477 -0.6531L0.074 -0.6476L0.1002 -0.6393L0.1264 -0.6283L0.1527 -0.6145L0.1789 -0.598L0.2052 -0.5787L0.2314 -0.5566L0.2577 -0.5319L0.2839 -0.5043L0.3101 -0.474L0.3364 -0.4409L0.3626 -0.4051L0.3889 -0.3666L0.4151 -0.3252L0.4414 -0.2812L0.4664 -0.2364L0.4891 -0.193L0.5094 -0.151L0.5272 -0.1104L0.5428 -0.0711L0.5559 -0.0332L0.5666 0.0033L0.575 0.0384L0.5809 0.0721L0.5845 0.1045L0.5857 0.1355L0.5845 0.1651L0.5809 0.1934L0.575 0.2202L0.5666 0.2457L0.5559 0.2698L0.5428 0.2925L0.5272 0.3139L0.5094 0.3338L0.4891 0.3524L0.4664 0.3697L0.4414 0.3855L0.4139 0.4L0.3841 0.4131L0.3519 0.4248L0.3173 0.4351L0.2803 0.444L0.241 0.4516L0.1992 0.4578L0.1551 0.4626L0.1086 0.4661L0.0596 0.4681L0.0084 0.4688L-0.0429 0.4681L-0.0919 0.4661L-0.1384 0.4626L-0.1825 0.4578L-0.2243 0.4516L-0.2636 0.444L-0.3006 0.4351L-0.3352 0.4248L-0.3674 0.4131L-0.3972 0.4L-0.4247 0.3855L-0.4497 0.3697L-0.4724 0.3524L-0.4927 0.3338L-0.5105 0.3139L-0.5261 0.2925L-0.5392 0.2698L-0.5499 0.2457L-0.5583 0.2202L-0.5642 0.1934L-0.5678 0.1651L-0.569 0.1355L-0.5678 0.1045L-0.5642 0.0721L-0.5583 0.0384L-0.5499 0.0033L-0.5392 -0.0332L-0.5261 -0.0711L-0.5105 -0.1104L-0.4927 -0.151L-0.4724 -0.193L-0.4497 -0.2364L-0.4247 -0.2812Z

real    0m0.385s
user    0m0.453s
sys     0m0.070s


回転させると三角形の内側に収まらなくなりますね。ライブラリからも回転用のAPIが提供されているみたいですが、オリジナルな三角形のパスコマンドを除けば、達成したい形は得られるので、ここでは細かいことは気にしません。


辺の数が3つ、次数が2個、時計回り30度回転の場合


回転させない場合の結果は以下になります。

$ time node index.js
M1 0L-0.5 0.866L-0.5 -0.866Z
M-0.5 0L-0.4993 -0.0513L-0.4972 -0.1002L-0.4938 -0.1467L-0.489 -0.1909L-0.4828 -0.2326L-0.4752 -0.272L-0.4663 -0.309L-0.4559 -0.3435L-0.4442 -0.3758L-0.4311 -0.4056L-0.4167 -0.433L-0.4008 -0.4581L-0.3836 -0.4807L-0.365 -0.501L-0.345 -0.5189L-0.3237 -0.5344L-0.301 -0.5475L-0.2769 -0.5583L-0.2514 -0.5666L-0.2245 -0.5726L-0.1963 -0.5762L-0.1667 -0.5774L-0.1357 -0.5762L-0.1033 -0.5726L-0.0696 -0.5666L-0.0344 -0.5583L0.0021 -0.5475L0.0399 -0.5344L0.0792 -0.5189L0.1198 -0.501L0.1618 -0.4807L0.2052 -0.4581L0.25 -0.433L0.2941 -0.4068L0.3354 -0.3805L0.374 -0.3543L0.4098 -0.328L0.4428 -0.3018L0.4731 -0.2756L0.5007 -0.2493L0.5255 -0.2231L0.5475 -0.1968L0.5668 -0.1706L0.5833 -0.1443L0.5971 -0.1181L0.6081 -0.0919L0.6164 -0.0656L0.6219 -0.0394L0.6247 -0.0131L0.6247 0.0131L0.6219 0.0394L0.6164 0.0656L0.6081 0.0919L0.5971 0.1181L0.5833 0.1443L0.5668 0.1706L0.5475 0.1968L0.5255 0.2231L0.5007 0.2493L0.4731 0.2756L0.4428 0.3018L0.4098 0.328L0.374 0.3543L0.3354 0.3805L0.2941 0.4068L0.25 0.433L0.2052 0.4581L0.1618 0.4807L0.1198 0.501L0.0792 0.5189L0.0399 0.5344L0.0021 0.5475L-0.0344 0.5583L-0.0696 0.5666L-0.1033 0.5726L-0.1357 0.5762L-0.1667 0.5774L-0.1963 0.5762L-0.2245 0.5726L-0.2514 0.5666L-0.2769 0.5583L-0.301 0.5475L-0.3237 0.5344L-0.345 0.5189L-0.365 0.501L-0.3836 0.4807L-0.4008 0.4581L-0.4167 0.433L-0.4311 0.4056L-0.4442 0.3758L-0.4559 0.3435L-0.4663 0.309L-0.4752 0.272L-0.4828 0.2326L-0.489 0.1909L-0.4938 0.1467L-0.4972 0.1002L-0.4993 0.0513L-0.5 0Z

real    0m0.383s
user    0m0.446s
sys     0m0.074s


ピタッとしている方がしっくりきますね。


辺の数が3つ、次数が2個、時計回り0度回転の場合


今回はいつもよりバリエーション増やしてやってみようと思います。

以後、重みはランダムで指定して、knotsは指定せずに実行しています。
ランダムで指定した重みの値に関してはコンソールに出力しています。

辺の数が4つ、次数が2個、時計回り0度回転の場合です。

$ time node index.js
weights [ 7, 5, 2, 6 ]
M1 0L0 1L-1 0L0 -1Z
M-0.25 -0.75L-0.22 -0.7772L-0.1906 -0.7988L-0.1615 -0.8154L-0.1327 -0.8276L-0.1041 -0.8358L-0.0754 -0.8403L-0.0468 -0.8415L-0.018 -0.8396L0.011 -0.8349L0.0403 -0.8275L0.0698 -0.8176L0.0998 -0.8052L0.1302 -0.7906L0.161 -0.7737L0.1925 -0.7547L0.2245 -0.7336L0.2572 -0.7104L0.2905 -0.6851L0.3247 -0.6579L0.3596 -0.6286L0.3954 -0.5973L0.4322 -0.564L0.4699 -0.5286L0.5087 -0.4911L0.5484 -0.4515L0.5859 -0.4122L0.62 -0.3737L0.6507 -0.3362L0.6783 -0.2994L0.7028 -0.2633L0.7243 -0.2278L0.7428 -0.1929L0.7583 -0.1583L0.771 -0.1242L0.7809 -0.0903L0.7879 -0.0566L0.7921 -0.0231L0.7935 0.0104L0.7919 0.0439L0.7875 0.0775L0.7801 0.1112L0.7697 0.1452L0.7561 0.1794L0.7393 0.2141L0.7192 0.2493L0.6957 0.2851L0.6685 0.3216L0.6375 0.3589L0.6024 0.3972L0.5637 0.4361L0.5249 0.4738L0.4865 0.5099L0.4486 0.5444L0.411 0.5772L0.3739 0.6083L0.3371 0.6375L0.3007 0.6649L0.2647 0.6904L0.229 0.7139L0.1937 0.7352L0.1586 0.7544L0.1239 0.7712L0.0894 0.7856L0.0552 0.7974L0.0212 0.8064L-0.0125 0.8125L-0.046 0.8154L-0.0794 0.815L-0.1126 0.8108L-0.1457 0.8027L-0.1787 0.7902L-0.2116 0.773L-0.2445 0.7506L-0.2775 0.7223L-0.3103 0.6881L-0.3423 0.6486L-0.3731 0.6038L-0.4022 0.5538L-0.4291 0.4985L-0.4535 0.4383L-0.4747 0.3736L-0.4925 0.3049L-0.5065 0.2328L-0.5164 0.1582L-0.522 0.0819L-0.5232 0.0047L-0.5201 -0.0723L-0.5127 -0.1483L-0.5013 -0.2224L-0.4863 -0.294L-0.4678 -0.3622L-0.4464 -0.4265L-0.4225 -0.4867L-0.3966 -0.5423L-0.3689 -0.5931L-0.3401 -0.6393L-0.3105 -0.6807L-0.2803 -0.7176L-0.25 -0.75Z

real    0m0.386s
user    0m0.399s
sys     0m0.137s


辺の数が4つ、次数が2個、時計回り0度回転の場合


辺の数が5つ、次数が2個、時計回り0度回転の場合です。


$ time node index.js
weights [ 9, 6, 3, 10, 10 ]
M1 0L0.309 0.9511L-0.809 0.5878L-0.809 -0.5878L0.309 -0.9511Z
M-0.25 -0.7694L-0.1942 -0.7862L-0.1398 -0.7998L-0.0868 -0.8103L-0.035 -0.8177L0.0156 -0.8219L0.0649 -0.8229L0.113 -0.8208L0.16 -0.8156L0.2059 -0.8071L0.2506 -0.7954L0.2943 -0.7805L0.337 -0.7622L0.3786 -0.7406L0.4193 -0.7156L0.4589 -0.6872L0.4976 -0.6552L0.5354 -0.6197L0.5723 -0.5805L0.6082 -0.5376L0.6432 -0.491L0.6761 -0.4436L0.7061 -0.397L0.7334 -0.3509L0.7579 -0.3055L0.7795 -0.2605L0.7983 -0.2161L0.8142 -0.172L0.8271 -0.1282L0.837 -0.0847L0.8438 -0.0414L0.8473 0.0018L0.8476 0.045L0.8444 0.0882L0.8376 0.1315L0.8271 0.175L0.8126 0.2188L0.7938 0.2629L0.7707 0.3076L0.7428 0.3529L0.7102 0.3988L0.6763 0.4435L0.6421 0.4868L0.6073 0.5284L0.5722 0.5682L0.5365 0.6062L0.5003 0.6422L0.4635 0.676L0.4261 0.7075L0.388 0.7365L0.3492 0.7629L0.3097 0.7863L0.2693 0.8067L0.228 0.8237L0.1857 0.837L0.1424 0.8464L0.0979 0.8514L0.0521 0.8516L0.005 0.8467L-0.0437 0.836L-0.0944 0.8187L-0.1485 0.7935L-0.2053 0.7598L-0.264 0.7177L-0.3237 0.6676L-0.3832 0.6101L-0.4414 0.5462L-0.4973 0.4771L-0.5498 0.4042L-0.5981 0.3289L-0.6417 0.2528L-0.68 0.1772L-0.7129 0.1033L-0.7405 0.0321L-0.7628 -0.0356L-0.7802 -0.0992L-0.7931 -0.1583L-0.8019 -0.2128L-0.807 -0.2627L-0.809 -0.308L-0.8077 -0.3486L-0.8025 -0.3849L-0.794 -0.4175L-0.7823 -0.4472L-0.7679 -0.4745L-0.7509 -0.4998L-0.7314 -0.5235L-0.7095 -0.5458L-0.6852 -0.567L-0.6586 -0.5873L-0.6297 -0.6068L-0.5984 -0.6257L-0.5647 -0.6441L-0.5284 -0.6622L-0.4896 -0.6801L-0.4479 -0.6978L-0.4033 -0.7155L-0.3556 -0.7333L-0.3046 -0.7512L-0.25 -0.7694Z

real    0m0.393s
user    0m0.481s
sys     0m0.061s


辺の数が5つ、次数が2個、時計回り0度回転の場合


辺の数が6つ、次数が2個、時計回り0度回転の場合です。


$ time node index.js
weights [ 6, 10, 9, 2, 10, 7 ]
M1 0L0.5 0.866L-0.5 0.866L-1 0L-0.5 -0.866L0.5 -0.866Z
M-0.0882 -0.866L-0.0295 -0.8649L0.029 -0.8613L0.0872 -0.8553L0.1448 -0.8465L0.2018 -0.835L0.2578 -0.8205L0.3127 -0.8029L0.3662 -0.7821L0.4181 -0.7581L0.4681 -0.7306L0.516 -0.6997L0.5616 -0.6653L0.6045 -0.6273L0.6446 -0.5858L0.6816 -0.5407L0.7153 -0.492L0.7454 -0.4398L0.7719 -0.384L0.7944 -0.3251L0.8127 -0.2635L0.8267 -0.1998L0.8362 -0.1344L0.8413 -0.068L0.8419 -0.0011L0.8383 0.0657L0.8306 0.1318L0.8191 0.1967L0.804 0.2601L0.7858 0.3214L0.7646 0.3805L0.7409 0.4369L0.7151 0.4906L0.6875 0.5413L0.6588 0.5876L0.6293 0.6285L0.5989 0.6648L0.5676 0.697L0.5351 0.7255L0.5014 0.7507L0.4662 0.7729L0.4294 0.7923L0.391 0.8091L0.3506 0.8236L0.3081 0.8358L0.2634 0.8459L0.216 0.8538L0.1659 0.8598L0.1127 0.8638L0.056 0.8658L-0.0034 0.8659L-0.0601 0.8653L-0.1133 0.8639L-0.1634 0.8618L-0.2104 0.8589L-0.2547 0.8551L-0.2964 0.8505L-0.3357 0.8448L-0.3727 0.838L-0.4074 0.8298L-0.44 0.8202L-0.4706 0.8088L-0.499 0.7952L-0.5254 0.7792L-0.5496 0.7599L-0.5715 0.7368L-0.5909 0.7086L-0.6091 0.6708L-0.6274 0.6187L-0.645 0.551L-0.6611 0.4671L-0.6749 0.3677L-0.6853 0.2548L-0.6918 0.1321L-0.6938 0.0042L-0.6911 -0.1235L-0.6841 -0.2457L-0.6733 -0.358L-0.6596 -0.4571L-0.6438 -0.5414L-0.6267 -0.6105L-0.6092 -0.6648L-0.5918 -0.7057L-0.5749 -0.7355L-0.5575 -0.7589L-0.5395 -0.778L-0.5205 -0.7939L-0.5005 -0.8071L-0.4793 -0.8182L-0.4567 -0.8277L-0.4324 -0.8357L-0.4063 -0.8425L-0.3781 -0.8483L-0.3476 -0.8531L-0.3142 -0.8571L-0.2777 -0.8603L-0.2375 -0.8628L-0.1931 -0.8646L-0.1436 -0.8657L-0.0882 -0.866Z

real    0m0.375s
user    0m0.426s
sys     0m0.088s



辺の数が6つ、次数が2個、時計回り0度回転の場合


いろいろ形が変化して面白いですね。

合成したポリゴンをもとにnurbsライブラリを使っても、個性ある面白い図形が出てきそうですね。


ポリゴンの合成に関しては以下の記事で紹介していました。




また、重みづけのレンジを三角関数とか挟んで時間で変化させたら、noise系関数とベジェ曲線コマンドを使って実現するやり方と同じ手数ぐらいでよりハンディにモーフィングアニメーション等はやりやすくなるのではと思いました。


最近では、Twitterでモックアップ動画を公開しているので、こちらもよかったら、覗いてみてください。


最後に、Udemyでコースを公開しました。
良かったら覗いてみてください。


また、コースの内容紹介記事は以下になります。


簡単ですが、以上です。

この記事が気に入ったらサポートをしてみませんか?