{p:2,q:1}, {p:2,q:3}, {p:2,q:5}, {p:2,q:7},
{p:3,q:8}, {p:4,q:7}, {p:2,q:9}, {p:9,q:4}
const morphTypeIds = [0,1];
const patternIds = [0,0];
let easeType = "easeInOutCirc";
const MyEasing = new p5wgex.Easing();
const MORPH_DURATION = 4500;
matcap0 = loadImage(`https://inaridarkfox4231.github.io/assets/sphereMaps/matcap${0}.jpg`);
const _timer = new p5wgex.Timer();
createCanvas(window.innerWidth*0.95, window.innerHeight*0.95, WEBGL);
_node = new p5wgex.RenderNode(this._renderer.GL);
const objectIndices = new Array(20);
for(let k=0; k<20; k++){ objectIndices[k]=k; }
const mu = p5wgex.meshUtil;
const tube = createModifiableTube({
tubeDetail:200, bandDetail:16
mu.regist(_node, tube, "tubes", {otherAttrs:[
{size:1, name:"aIndex", data:objectIndices, divisor:1}
const cam = new p5wgex.PerspectiveCamera({
w:width, h:height, eye:[4,0,2], top:[0,0,1]
const CC = new p5wgex.CameraController(this.canvas, {dblclick:true});
CC.setParam({rotationMode:"free"});
CC.registCamera("cam", cam);
LS = new p5wgex.StandardLightingSystem(_node);
LS.addAttr("float", "aIndex");
LS.addUniform("float", "uRatio", "vs");
LS.addUniform("ivec2", "uMorphTypeId", "vs");
LS.addUniform("vec2", "uPrevPattern", "vs");
LS.addUniform("vec2", "uNextPattern", "vs");
LS.addStruct("axisSystem", {
n:"vec3", b:"vec3", t:"vec3"
LS.addStruct("geometry", {
LS.addConstant("float", "PI", 3.14159, "vs");
LS.addConstant("float", "TAU", 6.28318, "vs");
vec3 torusKnot(in float t, in vec4 data){
// tは0~1. 1.0, 0.4, p, q. ディテールは4000でいこうね
(data.x + data.y*cos(t*TAU*data.w)) * cos(t*TAU*data.z),
(data.x + data.y*cos(t*TAU*data.w)) * sin(t*TAU*data.z),
data.y * sin(t*TAU*data.w)
void setAxisSystem(inout axisSystem sys, in float t, in vec2 ptn){
vec3 v = torusKnot(t, vec4(1.0, 0.4, ptn));
vec3 vPrev = torusKnot(t-1.0/4000.0, vec4(1.0, 0.4, ptn));
vec3 vNext = torusKnot(t+1.0/4000.0, vec4(1.0, 0.4, ptn));
vec3 tang = normalize(vNext-v);
vec3 preTang = normalize(v-vPrev);
vec3 nVec = normalize(tang - preTang);
sys.b = cross(sys.t, sys.n);
geometry getTorusKnotGeometry(in geometry g, in float index, in vec2 ptn){
float t = (index + g.p.z)*0.05;
setAxisSystem(sys, t, ptn);
vec3 center = torusKnot(t, vec4(1.0, 0.4, ptn));
geom.n = g.n.x * sys.n + g.n.y * sys.b + g.n.z * sys.t;
geom.p = center + 0.1 * geom.n;
geometry getRingGeometry(in geometry g, in float index, in vec2 ptn){
vec3 torusCenter = torusKnot(index*0.05 + 0.025, vec4(1.0, 0.4, ptn));
float torusAngle = (g.p.z-0.5)*3.14159*2.0;
float torusRadius = 0.02;
setAxisSystem(sys, index*0.05 + 0.025, ptn);
vec3 centerNormal = g.n.x * sys.n + g.n.y * sys.b;
geom.n = cos(torusAngle)*centerNormal + sin(torusAngle)*sys.t;
geom.p = torusCenter + 0.1*centerNormal + torusRadius * geom.n;
geometry getGeometry(in geometry g, in float index, in vec2 ptn, in int id){
if(id==1) return getTorusKnotGeometry(g, index, ptn);
return getRingGeometry(g, index, ptn);
// aIndexとposition.zでフェッチ
geometry geom0 = getGeometry(g, aIndex, uPrevPattern, uMorphTypeId.x);
// 真ん中を取ってそこでの値を元にしてトーラス上の場所を決める
geometry geom1 = getGeometry(g, aIndex, uNextPattern, uMorphTypeId.y);
position = mix(geom0.p, geom1.p, uRatio);
normal = normalize(mix(geom0.n, geom1.n, uRatio));
LS.addUniform("sampler2D", "uMatcap", "fs");
// matcapに使うのはvViewNormalです。
vec3 n = vViewNormal*0.99;
vec2 suv = 0.5*n.xy + 0.5;
color = texture(uMatcap, suv);
LS.registPainter("tubes");
_node.registTexture("matcap", {src:matcap0});
_node.registFBO("matcapGold", {w:matcap0.width, h:matcap0.height});
_node.bindFBO("matcapGold");
type:"color", target:"skyblue"
type:"tex", target:"matcap"
_node.registTexture("info", (function(){
const gr = createGraphics(width, height);
gr.textSize(Math.min(width,height)/20);
gr.textAlign(CENTER,CENTER);
gr.translate(width*0.5, height*0.1);
const _p = patternData[patternIds[0]].p;
const _q = patternData[patternIds[0]].q;
infoText = `type (${_p}, ${_q})`;
patternIds[1] = (patternIds[1] + 1) % patternData.length;
easeType = "easeInOutSine";
patternIds[0] = (patternIds[0] + 1) % patternData.length;
easeType = "easeInOutCirc";
eventIndex = (eventIndex + 1) % 3;
_timer.initialize("morph", {duration:MORPH_DURATION, completeFunction:execute});
_timer.initialize("transform");
from:{x:0,y:0}, to:{x:1,y:1}, stops:[0,0.497,0.503,1],
colors:["black","navy","navy","black"]
_node.updateTexture("info", (gr)=>{
_node.renderTexture("tex", "info", {blend:"blend"});
const ease = MyEasing.get(easeType);
let ratio = _timer.getProgress("morph");
ratio = ease(Math.min(Math.max(ratio*1.2-0.1,0),1));
_node.use("tubes", "tubes");
_node.setFBOtexture2D("uMatcap", "matcapGold");
_node.setUniform("uRatio", ratio);
_node.setUniform("uMorphTypeId", morphTypeIds);
const ptn0 = patternData[patternIds[0]];
const ptn1 = patternData[patternIds[1]];
_node.setUniform("uPrevPattern", [ptn0.p, ptn0.q]);
_node.setUniform("uNextPattern", [ptn1.p, ptn1.q]);
const t = _timer.getElapsed("transform");
LS.setLightingUniforms({cameraBase:true});
LS.setTransform([{rz:t*0.5}]).setMatrixUniforms();
_node.bindIBO("tubesIBO");
_node.drawElementsInstanced("triangles", {instanceCount:20});
function createModifiableTube(params = {}){
r = 1, h = 1, tubeDetail = 100, bandDetail = 16, sphereDetail = 8
const g = p5wgex.createGeom();
for(let i=0; i<=tubeDetail; i++){
const z = h*(tubeDetail-i)/tubeDetail;
for(let k=0; k<bandDetail; k++){
const angle = Math.PI*2*k/bandDetail;
g.n.push(Math.cos(angle), Math.sin(angle), 0);
for(let i=0; i<tubeDetail; i++){
const offset = i*bandDetail;
for(let k=0; k<bandDetail; k++){
const ld = lu + bandDetail;
const ru = (k < bandDetail - 1 ? offset+k+1 : offset);
const rd = ru + bandDetail;
g.f.push(lu, ld, ru, ru, ld, rd);