package main
import (
"math"
"syscall/js"
"github.com/ryomak/p5go"
)
var (
video js.Value // Webカメラ映像のグローバル変数
prevPixels js.Value // 前フレームのピクセルデータ
pointerX, pointerY float64 // 重み付き平均から得た動体の位置
smoothX, smoothY float64 // EMA による滑らかな位置
prevPointerX, prevPointerY float64 // 前フレームの滑らか位置(波発生用)
waves []Wave // 発生中の波エフェクト
)
const (
cellSize = 30 // セル単位のサイズ(ピクセル)
threshold = 100.0 // 差分とみなす閾値
movementThreshold = 100.0 // 前フレームとの位置差がこれを超えたら波を発生
smoothingFactor = 0.3 // EMA の係数(0~1、値が大きいほど素早く追従)
)
// Wave は波エフェクトを表します
type Wave struct {
x, y float64 // 発生位置
radius float64 // 現在の半径
alpha float64 // 描画時の透明度(0~255)
maxRadius float64 // 最大半径(例: 200)
}
func setup(c *p5go.Canvas) {
// キャンバスサイズ 500x400
c.CreateCanvas(400, 400)
// Webカメラ映像の取得
video = c.CreateCapture("VIDEO")
video.Call("hide") // video要素自体は非表示
// FPS を 7 に設定
c.FrameRate(7)
}
func draw(c *p5go.Canvas) {
// video のピクセルデータ更新
video.Call("loadPixels")
curPixels := video.Get("pixels")
c.Background(0)
width := int(c.Width())
height := int(c.Height())
var sumWeight, sumX, sumY float64
// 前フレームとの差分から動いている部分の重み付き平均位置を算出
if !prevPixels.IsUndefined() {
for y := 0; y < height; y += cellSize {
for x := 0; x < width; x += cellSize {
index := (y*width + x) * 4
rDiff := math.Abs(curPixels.Index(index).Float() - prevPixels.Index(index).Float())
gDiff := math.Abs(curPixels.Index(index+1).Float() - prevPixels.Index(index+1).Float())
bDiff := math.Abs(curPixels.Index(index+2).Float() - prevPixels.Index(index+2).Float())
diff := rDiff + gDiff + bDiff
if diff > threshold {
sumX += float64(x) * diff
sumY += float64(y) * diff
sumWeight += diff
}
}
}
}
// もし動いている部分があれば、重み付き平均位置を算出
if sumWeight > 0 {
pointerX = sumX / sumWeight
pointerY = sumY / sumWeight
// 初回の場合は EMA の初期値として設定
if smoothX == 0 && smoothY == 0 {
smoothX = pointerX
smoothY = pointerY
} else {
// EMA を用いて滑らかに更新
smoothX = (1-smoothingFactor)*smoothX + smoothingFactor*pointerX
smoothY = (1-smoothingFactor)*smoothY + smoothingFactor*pointerY
}
} // 動きがなければ前回値を維持
// 前フレームとの位置差があれば、波エフェクトを発生させる
dx := smoothX - prevPointerX
dy := smoothY - prevPointerY
dist := math.Sqrt(dx*dx + dy*dy)
if dist > movementThreshold {
newWave := Wave{
x: smoothX,
y: smoothY,
radius: 10,
alpha: 255,
maxRadius: 200,
}
waves = append(waves, newWave)
}
// 更新前の滑らかな位置を保存
prevPointerX = smoothX
prevPointerY = smoothY
// 現在のピクセルデータを次回用に保存
prevPixels = curPixels
// 波エフェクトの更新&描画
newWaves := []Wave{}
for _, wave := range waves {
wave.radius += 10 // 拡大速度
wave.alpha -= 25 // 消失速度
if wave.alpha > 0 && wave.radius < wave.maxRadius {
newWaves = append(newWaves, wave)
}
c.NoFill()
c.Stroke(255, 0, 0, wave.alpha)
c.Ellipse(wave.x, wave.y, wave.radius, wave.radius)
}
waves = newWaves
// 滑らかに更新された動体の位置に小さな青い円を描画(ポインタ)
c.NoStroke()
c.Fill(0, 0, 255)
c.Ellipse(smoothX, smoothY, 20, 20)
}
func main() {
p5go.Run("#canvas-detail",
p5go.Setup(setup),
p5go.Draw(draw),
)
select {}
}