xxxxxxxxxx
// 2023-05-04
// p5Lightex.
// foxIAになったんだっけ。exIAはもう死んだ。
// まあ、気が向いたら復活させればいいや。どうせ誰も見ないでしょ?
// EasyImageとかいろいろ死んだ
// 2023-11-20
// foxIAの仕様変更にともなうバグを修正
// 上下に描画するアイデアを導入
// 参考:https://qiita.com/inaba_darkfox/items/af1aaea412ef43bb718e
// 1.8,0に更新
// しません。1.7.0まででいいです。pagurekが_triangulateをまたいじくりまわしたせいで
// evenoddが死んでますね。おそらくcontourほにゃららが原因だと思う。
// まあ実質1.7.0がいわゆるstableって感じだし。1.8はバグだらけで実験用って感じだし。
// 1.7.0まででいいと思います。
// 結論が出ました。バグではないそうです。
// それでは困るので、このスケッチは永久に1.7.0です。
// 以上
// sayoさんの元ネタ:https://openprocessing.org/sketch/1412630
// tweet:https://x.com/_sayo_y/status/1473488472375640067
// 2024-05-20
// 自作テッセレーションで書き換え
// bgCam不要
// 自作テッセレートなので1.9.3でもevenoddが適用されます
// OK!
// というわけで1.9.3にしました
// 2024-05-21
// カリングの問題を修正しました
// 2024-05-28
// 1.9.4
// もう_triangulateがどんなにおかしくなろうが
// 知ったこっちゃないです
// forkを非公開にする変な人がいるんだけど
// Openだよね。Openって名前付いてるよね?
// 喧嘩売ってんのかな
// まあ、いいけどね。知らんわ。
// ちなみにこれ独自の外部ライブラリに頼ってるから
// こっちの都合次第では崩壊するけど。
// まあ、どうでもいいわな。
// 2024-08-05
// 1.10.0に更新
// fisceToyBoxで整理
// 自前のテッセレーション
// 2024-09-30
// 1.11.0になったよ
// へぇ。それで?
// 2024-10-13
// スクロールのバグを修正
// 全画面だと気づきにくいけど重要なので。
// --------------------------------------------------------------- //
// setting.
// 必須。ただしライブラリには含めず、外から指示する方向で。
// foxIAで禁じているので特に宣言しなくていです
//document.oncontextmenu = (e) => { e.preventDefault(); }
// --------------------------------------------------------------- //
// global.
let IA;
let bg, info;
let baseCloud, cloudImg;
let geomId = 0;
let shapes;
// --------------------------------------------------------------- //
// loading.
// 毎度おなじみループ雲画像(便利)
function preload(){
cloudImg = loadImage("https://inaridarkfox4231.github.io/assets/texture/cloud.png");
}
// --------------------------------------------------------------- //
// main.
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
pixelDensity(1);
// これでいいの??
IA = new foxIA.Interaction(this.canvas, {factory:(function (){
return new ShapePointer(this.width, this.height, this._renderer);
}).bind(this)});
// 初期化したら後には戻れない...
//IA.initialize(this.canvas); // canvasで初期化
fill(255);
noStroke();
shapes = new CrossReferenceArray();
bg = createGraphics(width, height); // bg.
baseCloud = createGraphics(width, height, WEBGL); // baseCloud.
info = createGraphics(width, height); // info.
info.textStyle(ITALIC);
info.translate(width / 2, height / 2);
// カリングを適用します
// テッセレーションのサブデータとして島の輪郭を取得できるんですが
// それが時計回りなので
// それを使って側面をテッセレートします
// これによりちゃんと側面が時計回りになりますね
// なおこの手の(contourを時計回りに取得する)技術は現行のp5にはないです
// 自前で作るしかない
// もっというとこの手のスケッチが目的、かつカリング無視すれば要らないんですが
// 輪郭線でなんかしたい場合に方法が無いんですよ
const gl = this._renderer.GL;
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.FRONT);
}
function draw() {
shapes.loop("updateMove");
clear();
drawBackground(width, height, millis()/12000, millis()/16000);
directionalLight(128, 128, 128, 0.5, 0.5, -1);
ambientLight(128);
specularMaterial(64);
shapes.loop("display");
info.clear();
shapes.loop("drawGuide", [info]);
if (shapes.length===0) {
const TEXT_SIZE = min(width, height)*0.04;
info.fill(0);
info.noStroke();
info.textSize(min(width, height)*0.04);
info.textAlign(CENTER, CENTER);
info.text("Draw a shape.", 0, -TEXT_SIZE*0.65);
info.text("Mouse, stylus pen and touch are available.", 0, TEXT_SIZE*0.65);
} else {
info.noStroke();
info.fill(0);
info.textAlign(LEFT, TOP);
info.textSize(18);
info.text(frameRate().toFixed(3), -width/2 + 5, -height/2 + 5);
}
// どれよりも上に描画する. bgCam? そんなものいらないわ
push();
camera(0,0,1,0,0,0,0,1,0);
ortho(-1,1,-1,1,0,1);
translate(0,0,1);
noLights();
texture(info);
plane(2);
pop();
shapes.loopReverse("remove");
}
// 動く雲画像
function drawBackground(w, h, s, t){
s = fract(s);
t = fract(t);
baseCloud.texture(cloudImg);
baseCloud.textureMode(NORMAL);
baseCloud.textureWrap(REPEAT);
const wRatio = w/(3*cloudImg.width);
const hRatio = h/(3*cloudImg.height);
baseCloud.beginShape();
baseCloud.vertex(-w/2, -h/2, s, t);
baseCloud.vertex(w/2, -h/2, s + wRatio, t);
baseCloud.vertex(w/2, h/2, s + wRatio, t + hRatio);
baseCloud.vertex(-w/2, h/2, s, t + hRatio);
baseCloud.endShape();
bg.blendMode(BLEND);
bg.image(baseCloud, 0, 0);
bg.blendMode(SCREEN);
bg.background(64, 128, 255);
// どれよりも下に描画する. bgCam要らん。
push();
camera(0,0,1,0,0,0,0,1,0);
ortho(-1,1,-1,1,0,1);
noLights();
texture(bg);
plane(2);
pop();
}
// --------------------------------------------------------------- //
// createShape3D.
// utility for create shapes.
function findCenter(vectors) {
const center = createVector();
for (let i = 0; i < vectors.length; i++) {
center.add(vectors[i]);
}
center.div(vectors.length);
for (let i = 0; i < vectors.length; i++) {
vectors[i].sub(center);
}
return center;
}
function createShape3D(gId, vectors){
fisceToyBox.evenlySpacing(vectors, {minLength:24,closed:true});
fisceToyBox.quadBezierize(vectors, {detail:8, closed:true});
fisceToyBox.evenlySpacing(vectors, {minLength:12,closed:true});
const cycles = fisceToyBox.createDisjointPaths([vectors], {output:"cycle_vertices"});
const result = fisceToyBox.cyclesToCycles(cycles.cycles);
const mesh = fisceToyBox.createBoardMeshFromCycles({result:result, thick:20});
// バッファ作成
this._renderer.createBuffers(gId, mesh);
}
// --------------------------------------------------------------- //
// Pointer.
// PointerPrototypeの継承
// ShapeMeshをインタラクションによりジェネレートするユニット。
// うまくいきましたね。
class ShapePointer extends foxIA.PointerPrototype{
constructor(w, h, _gl){
super();
this.w = w;
this.h = h;
this.renderer = _gl;
this.shape = undefined;
}
mouseDownAction(e){
this.shape = new ShapeMesh(this.renderer);
this.shape.initialize(this.x - this.w/2, this.y - this.h/2);
shapes.add(this.shape); // ほんとはクラス名Shape3Dにしたいんですけどね
}
mouseMoveAction(e){
this.shape.addVertex(this.x - this.w/2, this.y - this.h/2);
}
mouseUpAction(){
this.shape.complete();
}
touchStartAction(t){
this.shape = new ShapeMesh(this.renderer);
this.shape.initialize(this.x - this.w/2, this.y - this.h/2);
shapes.add(this.shape); // ほんとはクラス名Shape3Dにしたいんですけどね
}
touchMoveAction(t){
this.shape.addVertex(this.x - this.w/2, this.y - this.h/2);
}
touchEndAction(t){
this.shape.complete();
}
}
// --------------------------------------------------------------- //
// shape3D.
// ShapeMesh(shape3D)
// addVertexはマウスないしはタッチポインタが動くたびに呼び出せばいい
// drawGuideは毎フレームですね
// 要するに発火時に両方に登録するわけ
class ShapeMesh{
constructor(_gl){
this.active = false;
this.closed = false;
this.completed = false;
this.vectors = [];
this.position = createVector();
this.movingSpeed = 0;
this.rotation = 0;
this.rotationSpeed = 0;
this.shapeColor = color(0); // 描画中の色
this.life = 3;
this.alive = true;
this.gId = -1;
this.renderer = _gl;
}
kill(){
this.alive = false;
}
initialize(x, y){
// x,yは事前にセンタリング済み
this.active = true;
this.vectors.push(createVector(x, y, 0));
}
addVertex(x, y){
if(this.completed){ return; }
// x,yは事前にセンタリング済み
// closedでない場合のみ発動
if (!this.closed) {
const v = createVector(x, y, 0);
if (this.vectors.length > 10 && v.dist(this.vectors[0]) < 20) {
// 頭とおしりがくっつくときに閉じるみたいですね
// 短すぎると登録できないようです(すぐに閉じるのを防ぐ)
this.closed = true;
// 閉じたときに色を決める
const col = fisceToyBox.hsv2rgb(Math.random(), 1, 1);
this.shapeColor = color(col.r*255, col.g*255, col.b*255);
} else if (this.vectors.length > 0 && v.dist(this.vectors[this.vectors.length - 1]) > 10) {
// 前の点からある程度離れているときに追加するみたい
// 10は長すぎる気もするけど
this.vectors.push(v);
}
}
}
drawGuide(gr){
if(this.completed){ return; }
gr.fill(this.shapeColor);
// 閉じている場合に色を変える
gr.stroke(this.shapeColor);
gr.strokeWeight(4);
gr.circle(this.vectors[0].x, this.vectors[0].y, 10);
gr.noFill();
gr.beginShape();
for (let i = 0; i < this.vectors.length; i++) {
gr.vertex(this.vectors[i].x, this.vectors[i].y);
}
// 閉じてない場合は円を描く
if(this.closed) {
gr.endShape(CLOSE);
} else {
gr.endShape();
gr.fill(this.shapeColor);
gr.circle(
this.vectors[this.vectors.length - 1].x,
this.vectors[this.vectors.length - 1].y,
10
);
}
}
complete(){
this.active = false;
if(this.closed){
// shapeのgeometryを構成する
this.createShape();
this.completed = true;
}else{
// closedでないのにcomplete処理が行われたら排除
this.kill();
}
}
createShape(){
this.position = findCenter(this.vectors);
this.movingSpeed = (random() < 0.5 ? 1 : -1) * random(0.1, 0.2);
this.rotationSpeed = (random() < 0.5 ? 1 : -1) * random(0.02, 0.05) / 16;
this.gId = `shape3D${geomId}`;
geomId++;
createShape3D(this.gId, this.vectors); // これをmodelで描画する
}
updateMove(){
if(!this.completed){ return; }
this.position.y += deltaTime * this.movingSpeed
this.rotation += deltaTime * this.rotationSpeed;
const SIZE_SCALE = min(width, height);
// 画面外に出たら排除
if(this.position.y < -height*2){
this.position.x = random(-width/2, width);
this.position.y = height*2;
this.position.z -= SIZE_SCALE;
this.life--;
}else if(this.position.y > height*2){
this.position.x = random(-width/2, width);
this.position.y = -height*2;
this.position.z -= SIZE_SCALE;
this.life--;
}
if(this.life===0){ this.kill(); }
}
display(){
if(!this.completed){ return; }
ambientMaterial(this.shapeColor);
translate(this.position);
rotateY(this.rotation);
//shape3D(i);
this.renderer.drawBuffers(this.gId);
resetMatrix();
}
remove(){
if(!this.alive){
// crossReferenceArrayを使って排除する
// 閉じずに開いた場合もこれでkillすればいいわね
shapes.remove(this);
}
}
}
// --------------------------------------------------------------- //
// utility.
// CrossReferenceArray.
class CrossReferenceArray extends Array{
constructor(){
super();
}
add(element){
this.push(element);
element.belongingArray = this; // 所属配列への参照
}
addMulti(elementArray){
// 複数の場合
elementArray.forEach((element) => { this.add(element); })
}
remove(element){
let index = this.indexOf(element, 0);
this.splice(index, 1); // elementを配列から排除する
}
loop(methodName, args = []){
if(this.length === 0){ return; }
// methodNameには"update"とか"display"が入る。まとめて行う処理。
for(let i = 0; i < this.length; i++){
this[i][methodName](args);
}
}
loopReverse(methodName, args = []){
if(this.length === 0){ return; }
// 逆から行う。排除とかこうしないとエラーになる。もうこりごり。
for(let i = this.length - 1; i >= 0; i--){
this[i][methodName](args);
}
}
clear(){
this.length = 0;
}
}