318 lines
40 KiB
HTML
318 lines
40 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Neural Particle - Scatter Effect</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { overflow: hidden; background-color: #000; }
|
|
canvas { display: block; }
|
|
#particle-container { width: 100vw; height: 100vh; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="particle-container"></div>
|
|
<script>
|
|
const CONFIG = {
|
|
effect: "scatter",
|
|
effectMode: 1,
|
|
particleSize: 191,
|
|
particleCount: 60000,
|
|
sceneScale: 1,
|
|
uploadedImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAYAAADbcAZoAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3BpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAyIDc5LmExY2QxMmY0MSwgMjAyNC8xMS8wOC0xNjowOToyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowODJGMTk3QTI1QjgxMUYwQkU3OEY1OEExNTUzMDRCNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCOUVCQUJGQTFDOTkxMUYxQThBM0VCOEU0MkIxNzEyRiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCOUVCQUJGOTFDOTkxMUYxQThBM0VCOEU0MkIxNzEyRiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZkOTA5MGM2LWQ3MGQtNGE0NC1hNzM1LTFjMWZmN2QwMTQ0YyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowODJGMTk3QTI1QjgxMUYwQkU3OEY1OEExNTUzMDRCNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ph5sOxgAACDsSURBVHja7N15sJ1lfQfwcwxgCKJiQUBIcrOBxC0UtUWhhgoJEEJAFChaHYgiKlortco4uFCnrWIVC1gZ6wzaFgk7yl6FIEiCyCJLQZLcJQsgoOwYAsnt73hpCnpDzrnPPe/6+cw8vPkP5nuZN/nm9zzv0xwcHGwAAABk4SUiAAAAFBAAAEABAQAAUEAAAAAFBAAAQAEBAAAUEAAAQAEBAABQQAAAAAUEAABAAQEAABQQAABAAQEAAFBAAAAABQQAAEABAQAAFBAAAEABAQAAUEAAAAAFBAAAQAEBAAAUEAAAQAEBAABQQAAAAAUEAABAAQEAABQQAABAAQEAAFBAAAAABQQAAEABAQAAFBAAAAAFBAAAUEAAAAAFBAAAQAEBAAAUEAAAAAUEAABQQAAAAAUEAABAAQEAABQQAAAABQQAAFBAAAAABQQAAEABAQAAFBAAAAAFBAAAUEAAAAAFBAAAQAEBAAAUEAAAAAUEAABQQAAAAAUEAABAAQEAABQQAAAABQQAAFBAAAAAFBAAAEABAQAAFBAAAAAFBAAAUEAAAAAUEAAAQAEBAAAUEAAAAAUEAABQQAAAABQQAABAAQEAABQQAAAABQQAAFBAAAAAFBAAAEABAQAAFBAAAAAFBAAAUEAAAAAUEAAAQAEBAAAUEAAAAAUEAABQQAAAABQQAABAAQEAABQQEQAAAAoIAACggAAAACggAACAAgIAAKCAAAAACggAAKCAAAAAKCAAAIACAgAAoIAAAAAKCAAAoIAAAAAoIAAAgAICAACggAAAAAoIAACggAAAACggAACAAgIAAKCAAAAACggAAKCAAAAAKCAAAIACAgAAoIAAAAAKCAAAoIAAAAAoIAAAgAICAACggAAAAAoIAACAAgIAACggAACAAgIAAKCAAAAACggAAIACAgAAKCAAAIACAgAAoIAAAAAKCAAAgAICAAAoIAAAgAICAACggAAAAAoIAACAAgIAACggAACAAgIAANA1m4iAFGPPffgdUgCAeln97q2ukQIKCHlZKAIAqJ2mCBgpW7AAAAAFBAAAUEAAAABGzBkQEg2KAACAtpmAAAAACggAAKCAAAAAKCAAAIACAgAAoIAAAAAKCAAAUGHuASFNUwQAALTPBAQAAFBAAACA6rEFi0SDIgAAoG0mIAAAgAICAAAoIAAAAAoIAACggAAAACggAABA9nyGlyQuQgcAoBMmIAAAgAICAABUjy1YpLEHCwAABYTsDIoAAIC22YIFAAAoIAAAgAICAACggAAAAAoIAACAAgIAACggAACAAgIAAKCAAAAACggAAMAf20QEJGkOygAAgLaZgAAAAAoIAACggAAAACggAACAAgIAAKCAAAAACggAAKCAAAAAKCAAAECJuAmdJM2Gm9ABAGifCQgAAJAZExDSNEUAAED7TEAAAAAFBAAAUEAAAAAUEAAAQAEBAABQQAAAAAUEAACoMPeAkMhN6AAAtM8EBAAAUEAAAAAFBAAAYMScASFNUwQAALTPBAQAAFBAAAAABQQAAEABAQAAFBAAAID1fAWLRG5CBwCgfSYgAACAAgIAACggAAAAI+YMCElchA4AgAKCBgIAQCHZggUAACggAACAAgIAAKCAAAAAxecQOonchA4AQPtMQAAAAAUEAABQQAAAABQQAABAAQEAAFBAAACA7PkML2maIgAAoH0mIAAAgAICAABUjy1YJHITOgAA7TMBAQAAFBAAAEABAQAAUEAAAAAFBAAAQAEBAACy5zO8JHEROgAACggaCAAACghV5CJCAADa5wwIAACggAAAAAoIAACAAgIAACggAAAACggAAKCAAAAACggAAIACAgAAlIib0EnTdBM6AADtMwEBAAAUEAAAQAEBAABQQAAAAAUEAABAAQEAABQQAABAAQEAAFBAAACAEnETOonchA4AgAJCRppNGQAA0D5bsAAAAAUEAABQQAAAABQQAABAAQEAAFBAAAAABQQAAKgw94CQyEWEAAC0zwQEAABQQAAAgOqxBYs0TREAANA+ExAAAEABAQAAFBAAAAAFBAAAUEAAAADW8xUsErmIEACA9pmAAAAACggAAKCAAAAAjJgzIKRxEzoAAAoI+gcAAEVkCxYAAKCAAAAACggAAMCIOQNCIhcRAgDQPhMQAABAAQEAABQQAAAABQQAAFBAAAAA1vMVLNK4Ch0AgA6YgAAAAAoIAABQPbZgkchFhHT1f67HxAAjsmmscWIAFBCA9i1/bPakHjFA515+Rd9B8bhAEkAR2YIFAAAoIAAAgAICAACggAAAAAoIAADAer6C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',
|
|
shape: "default",
|
|
interactionMode: "auto",
|
|
manualControlTarget: "camera"
|
|
};
|
|
try {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const particleSizeOverride = Number(urlParams.get("particleSize"));
|
|
const particleCountOverride = Number(urlParams.get("particleCount"));
|
|
const sceneScaleOverride = Number(urlParams.get("sceneScale"));
|
|
if (Number.isFinite(particleSizeOverride) && particleSizeOverride > 0) {
|
|
CONFIG.particleSize = particleSizeOverride;
|
|
}
|
|
if (Number.isFinite(particleCountOverride) && particleCountOverride > 0) {
|
|
CONFIG.particleCount = Math.floor(particleCountOverride);
|
|
}
|
|
if (Number.isFinite(sceneScaleOverride) && sceneScaleOverride > 0) {
|
|
CONFIG.sceneScale = sceneScaleOverride;
|
|
}
|
|
} catch (error) {}
|
|
let uploadedImage = CONFIG.uploadedImage;
|
|
let particleSize = CONFIG.particleSize;
|
|
const PARTICLE_COUNT = CONFIG.particleCount;
|
|
const SCENE_SCALE = CONFIG.sceneScale;
|
|
let sceneData = null;
|
|
let currentEffect = CONFIG.effect;
|
|
let currentShape = CONFIG.shape;
|
|
let interactionMode = CONFIG.interactionMode;
|
|
let manualControlTarget = CONFIG.manualControlTarget;
|
|
let effectIntensity = 1;
|
|
let targetEffectIntensity = 1;
|
|
let explosionTriggered = false;
|
|
let explosionTime = 0;
|
|
const cameraControlState = {
|
|
target: null,
|
|
yaw: 0,
|
|
pitch: 0,
|
|
distance: 45,
|
|
minDistance: 20,
|
|
maxDistance: 100,
|
|
isLeftDragging: false,
|
|
isRightDragging: false,
|
|
previousX: 0,
|
|
previousY: 0
|
|
};
|
|
const mouseState = { isDragging: false, previousX: 0, previousY: 0 };
|
|
const vertexShader = "uniform float uTime;uniform float uMorph;uniform float uPointSize;uniform int uEffectMode;uniform float uEffectIntensity;uniform float uExplosionTime;attribute vec3 targetPosition;attribute vec3 targetColor;attribute vec3 color;attribute vec3 randomOffset;varying vec3 vColor;varying float vDistance;vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec2 mod289(vec2 x){return x-floor(x*(1.0/289.0))*289.0;}vec3 permute(vec3 x){return mod289(((x*34.0)+1.0)*x);}float snoise(vec2 v){const vec4 C=vec4(0.211324865405187,0.366025403784439,-0.577350269189626,0.024390243902439);vec2 i=floor(v+dot(v,C.yy));vec2 x0=v-i+dot(i,C.xx);vec2 i1=(x0.x>x0.y)?vec2(1.0,0.0):vec2(0.0,1.0);vec4 x12=x0.xyxy+C.xxzz;x12.xy-=i1;i=mod289(i);vec3 p=permute(permute(i.y+vec3(0.0,i1.y,1.0)) + i.x + vec3(0.0,i1.x,1.0));vec3 m=max(0.5-vec3(dot(x0,x0),dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)),0.0);m=m*m;m=m*m;vec3 x=2.0*fract(p*C.www)-1.0;vec3 h=abs(x)-0.5;vec3 ox=floor(x+0.5);vec3 a0=x-ox;m*=1.79284291400159-0.85373472095314*(a0*a0+h*h);vec3 g;g.x=a0.x*x0.x+h.x*x0.y;g.yz=a0.yz*x12.xz+h.yz*x12.yw;return 130.0*dot(m,g);}void main(){vColor=mix(color,targetColor,uMorph);vec3 pos=mix(position,targetPosition,uMorph);vec3 originalPos=pos;float effectMix=uEffectIntensity;if(uEffectMode==0){float noise=sin(uTime*1.5+position.x*0.3)*cos(uTime*1.5+position.y*0.3);pos+=normalize(pos)*noise*(0.2*(1.0-uMorph));pos.x+=sin(uTime*0.3+position.z)*0.1;pos.y+=cos(uTime*0.3+position.x)*0.1;}else if(uEffectMode==1){vec3 scatterDir=normalize(pos+randomOffset*0.5);float scatterDist=length(pos)*0.5+randomOffset.x*3.0;vec3 scattered=pos+scatterDir*scatterDist*effectMix*2.5;float turb=snoise(pos.xy*0.3+uTime*0.5);scattered+=vec3(turb,turb*0.5,turb*0.3)*effectMix*1.5;pos=mix(originalPos,scattered,effectMix);}else if(uEffectMode==2){float explodeProgress=min(uExplosionTime*2.0,1.0);float returnProgress=max(0.0,(uExplosionTime-0.5)*2.0);vec3 explodeDir=normalize(pos+randomOffset);float explodeDist=(5.0+randomOffset.x*8.0)*sin(explodeProgress*3.14159);vec3 exploded=originalPos+explodeDir*explodeDist*effectMix;float spin=explodeProgress*6.28318*(0.5+randomOffset.y);exploded.x+=cos(spin)*explodeDist*0.3;exploded.z+=sin(spin)*explodeDist*0.3;pos=mix(originalPos,exploded,effectMix*(1.0-returnProgress*0.7));}else if(uEffectMode==3){float angle=atan(pos.z,pos.x);float radius=length(pos.xz);float height=pos.y;float spiralSpeed=uTime*2.0+height*0.3;float newAngle=angle+spiralSpeed*effectMix;float vortexPull=(1.0-abs(height)/20.0)*effectMix;float newRadius=radius*(1.0-vortexPull*0.5)+sin(uTime*3.0+height)*effectMix;float lift=effectMix*5.0*(1.0-radius/20.0);pos.x=cos(newAngle)*newRadius;pos.z=sin(newAngle)*newRadius;pos.y=height+lift*sin(uTime+radius);}else if(uEffectMode==4){float pulsePhase=uTime*2.5;float pulseFactor=1.0+sin(pulsePhase)*0.4*effectMix;float waveFactor=sin(pulsePhase+length(pos)*0.3)*0.3*effectMix;vec3 pulsed=pos*pulseFactor;pulsed+=normalize(pos)*waveFactor*3.0;float colorPulse=sin(pulsePhase*0.5)*0.5+0.5;vColor=mix(vColor,vec3(1.0,0.4,0.8),colorPulse*effectMix*0.3);pos=pulsed;}else if(uEffectMode==5){float waveX=sin(pos.x*0.5+uTime*2.0)*effectMix*3.0;float waveZ=cos(pos.z*0.5+uTime*1.5)*effectMix*2.0;float waveY=sin(pos.x*0.3+pos.z*0.3+uTime*2.5)*effectMix*4.0;waveY+=sin(pos.x*0.8-uTime*1.8)*effectMix*1.5;waveY+=cos(pos.z*0.6+uTime*1.2)*effectMix*1.0;pos.x+=waveX*0.3;pos.y+=waveY;pos.z+=waveZ*0.3;}vec4 mvPosition=modelViewMatrix*vec4(pos,1.0);float dist=length(pos);vDistance=dist;float sizeMultiplier=1.0;if(uEffectMode==2&&effectMix>0.1)sizeMultiplier=1.0+sin(uExplosionTime*10.0)*0.3;if(uEffectMode==4)sizeMultiplier=1.0+sin(uTime*2.5)*0.2*effectMix;gl_PointSize=(uPointSize/-mvPosition.z)*(1.2+sin(uTime*3.0+dist*0.15)*0.5)*sizeMultiplier;gl_Position=projectionMatrix*mvPosition;}";
|
|
const fragmentShader = "uniform float uTime;varying vec3 vColor;varying float vDistance;void main(){float dist=distance(gl_PointCoord,vec2(0.5));if(dist>0.5)discard;float strength=pow(1.0-dist*2.0,1.6);vec3 finalColor=vColor*2.0;float alpha=strength*(0.8+sin(vDistance*0.3+uTime)*0.2);gl_FragColor=vec4(finalColor,alpha);}";
|
|
function updateCameraFromState(camera) {
|
|
const cp = Math.cos(cameraControlState.pitch);
|
|
const sp = Math.sin(cameraControlState.pitch);
|
|
const cy = Math.cos(cameraControlState.yaw);
|
|
const sy = Math.sin(cameraControlState.yaw);
|
|
camera.position.set(
|
|
cameraControlState.target.x + cameraControlState.distance * sy * cp,
|
|
cameraControlState.target.y + cameraControlState.distance * sp,
|
|
cameraControlState.target.z + cameraControlState.distance * cy * cp
|
|
);
|
|
camera.lookAt(cameraControlState.target);
|
|
}
|
|
function initParticleCanvas() {
|
|
const container = document.getElementById("particle-container");
|
|
const width = window.innerWidth, height = window.innerHeight;
|
|
const scene = new THREE.Scene();
|
|
const camera = new THREE.PerspectiveCamera(35, width / height, 0.1, 1000);
|
|
camera.position.z = 45;
|
|
cameraControlState.target = new THREE.Vector3(0, 0, 0);
|
|
updateCameraFromState(camera);
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" });
|
|
renderer.setSize(width, height);
|
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
renderer.setClearColor(0x000000, 0);
|
|
container.appendChild(renderer.domElement);
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(PARTICLE_COUNT * 3);
|
|
const targetPositions = new Float32Array(PARTICLE_COUNT * 3);
|
|
const colors = new Float32Array(PARTICLE_COUNT * 3);
|
|
const targetColors = new Float32Array(PARTICLE_COUNT * 3);
|
|
const randomOffsets = new Float32Array(PARTICLE_COUNT * 3);
|
|
const greenColor = new THREE.Color(0x00ff66);
|
|
const brightWhite = new THREE.Color(0xffffff);
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
const i3 = i * 3;
|
|
const t = (Math.random() - 0.5) * 5.0;
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const radiusBase = 0.4 + Math.pow(Math.abs(t), 2.4);
|
|
const radius = radiusBase * (0.75 + Math.random() * 0.55);
|
|
let x = radius * Math.cos(angle) * 2.9 * SCENE_SCALE;
|
|
let z = radius * Math.sin(angle) * 2.9 * SCENE_SCALE;
|
|
let y = t * 7.5 * SCENE_SCALE;
|
|
positions[i3] = x; positions[i3 + 1] = y; positions[i3 + 2] = z;
|
|
targetPositions[i3] = x; targetPositions[i3 + 1] = y; targetPositions[i3 + 2] = z;
|
|
randomOffsets[i3] = (Math.random() - 0.5) * 2;
|
|
randomOffsets[i3 + 1] = (Math.random() - 0.5) * 2;
|
|
randomOffsets[i3 + 2] = (Math.random() - 0.5) * 2;
|
|
const color = Math.random() > 0.7 ? greenColor : brightWhite;
|
|
colors[i3] = color.r; colors[i3 + 1] = color.g; colors[i3 + 2] = color.b;
|
|
targetColors[i3] = color.r; targetColors[i3 + 1] = color.g; targetColors[i3 + 2] = color.b;
|
|
}
|
|
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
geometry.setAttribute("targetPosition", new THREE.BufferAttribute(targetPositions, 3));
|
|
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
geometry.setAttribute("targetColor", new THREE.BufferAttribute(targetColors, 3));
|
|
geometry.setAttribute("randomOffset", new THREE.BufferAttribute(randomOffsets, 3));
|
|
const material = new THREE.ShaderMaterial({
|
|
vertexShader, fragmentShader, transparent: true,
|
|
uniforms: { uTime: { value: 0 }, uMorph: { value: 0 }, uPointSize: { value: particleSize }, uEffectMode: { value: CONFIG.effectMode }, uEffectIntensity: { value: 1 }, uExplosionTime: { value: 0 } },
|
|
depthWrite: false, blending: THREE.AdditiveBlending
|
|
});
|
|
const points = new THREE.Points(geometry, material);
|
|
scene.add(points);
|
|
sceneData = { scene, camera, renderer, points, geometry, material, originalPositions: positions.slice(), targetPositions, originalColors: colors.slice(), targetColors };
|
|
if (uploadedImage) processImage(uploadedImage);
|
|
else if (currentShape && currentShape !== "default") generateShape(currentShape);
|
|
let time = 0, morphFactor = 0;
|
|
const effectModes = { "default": 0, "scatter": 1, "explode": 2, "vortex": 3, "pulse": 4, "wave": 5 };
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
time += 0.008;
|
|
if (!sceneData) return;
|
|
const { renderer, scene, camera, points, material } = sceneData;
|
|
if (interactionMode === "manual") {
|
|
if (manualControlTarget === "camera") {
|
|
updateCameraFromState(camera);
|
|
}
|
|
} else {
|
|
let rotationSpeed = 0.0025;
|
|
if (currentEffect === "vortex") rotationSpeed = 0.008 * (0.5 + effectIntensity);
|
|
else if (currentEffect === "explode" && explosionTriggered) rotationSpeed = 0.001;
|
|
points.rotation.y += rotationSpeed;
|
|
points.rotation.z += 0.001;
|
|
points.rotation.x = Math.sin(time * 0.15) * 0.12;
|
|
}
|
|
material.uniforms.uTime.value = time;
|
|
const targetMorph = uploadedImage ? 1.0 : 0.0;
|
|
morphFactor += (targetMorph - morphFactor) * 0.05;
|
|
material.uniforms.uMorph.value = morphFactor;
|
|
effectIntensity += (targetEffectIntensity - effectIntensity) * 0.08;
|
|
material.uniforms.uEffectIntensity.value = effectIntensity;
|
|
material.uniforms.uEffectMode.value = effectModes[currentEffect] || 0;
|
|
if (explosionTriggered) { explosionTime += 0.016; if (explosionTime > 2.0) explosionTime = 0; }
|
|
material.uniforms.uExplosionTime.value = explosionTime;
|
|
renderer.render(scene, camera);
|
|
}
|
|
animate();
|
|
window.addEventListener("resize", function() {
|
|
if (!sceneData) return;
|
|
sceneData.camera.aspect = window.innerWidth / window.innerHeight;
|
|
sceneData.camera.updateProjectionMatrix();
|
|
sceneData.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
sceneData.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
});
|
|
renderer.domElement.addEventListener("mousedown", function(e) {
|
|
if (interactionMode === "manual" && manualControlTarget === "camera") {
|
|
if (e.button === 0) {
|
|
cameraControlState.isLeftDragging = true;
|
|
} else if (e.button === 2) {
|
|
cameraControlState.isRightDragging = true;
|
|
}
|
|
cameraControlState.previousX = e.clientX;
|
|
cameraControlState.previousY = e.clientY;
|
|
}
|
|
if (interactionMode === "manual" && manualControlTarget === "object") {
|
|
mouseState.isDragging = true;
|
|
mouseState.previousX = e.clientX;
|
|
mouseState.previousY = e.clientY;
|
|
}
|
|
});
|
|
window.addEventListener("mousemove", function(e) {
|
|
if (!sceneData) return;
|
|
if (interactionMode === "manual" && manualControlTarget === "camera") {
|
|
const deltaX = e.clientX - cameraControlState.previousX;
|
|
const deltaY = e.clientY - cameraControlState.previousY;
|
|
if (cameraControlState.isLeftDragging) {
|
|
cameraControlState.yaw -= deltaX * 0.005;
|
|
cameraControlState.pitch -= deltaY * 0.005;
|
|
const maxPitch = Math.PI * 0.499;
|
|
if (cameraControlState.pitch > maxPitch) cameraControlState.pitch = maxPitch;
|
|
if (cameraControlState.pitch < -maxPitch) cameraControlState.pitch = -maxPitch;
|
|
} else if (cameraControlState.isRightDragging) {
|
|
const panScale = cameraControlState.distance * 0.002;
|
|
const forward = new THREE.Vector3();
|
|
sceneData.camera.getWorldDirection(forward);
|
|
const right = new THREE.Vector3().crossVectors(forward, sceneData.camera.up).normalize();
|
|
const up = new THREE.Vector3().copy(sceneData.camera.up).normalize();
|
|
cameraControlState.target.addScaledVector(right, -deltaX * panScale);
|
|
cameraControlState.target.addScaledVector(up, deltaY * panScale);
|
|
}
|
|
cameraControlState.previousX = e.clientX;
|
|
cameraControlState.previousY = e.clientY;
|
|
}
|
|
if (interactionMode === "manual" && manualControlTarget === "object" && mouseState.isDragging) {
|
|
const deltaX = e.clientX - mouseState.previousX;
|
|
const deltaY = e.clientY - mouseState.previousY;
|
|
sceneData.points.rotation.y += deltaX * 0.005;
|
|
sceneData.points.rotation.x += deltaY * 0.005;
|
|
mouseState.previousX = e.clientX;
|
|
mouseState.previousY = e.clientY;
|
|
}
|
|
});
|
|
window.addEventListener("mouseup", function() {
|
|
mouseState.isDragging = false;
|
|
cameraControlState.isLeftDragging = false;
|
|
cameraControlState.isRightDragging = false;
|
|
});
|
|
renderer.domElement.addEventListener("wheel", function(e) {
|
|
if (interactionMode === "manual" && manualControlTarget === "camera") {
|
|
e.preventDefault();
|
|
cameraControlState.distance += e.deltaY * 0.03;
|
|
if (cameraControlState.distance < cameraControlState.minDistance) {
|
|
cameraControlState.distance = cameraControlState.minDistance;
|
|
}
|
|
if (cameraControlState.distance > cameraControlState.maxDistance) {
|
|
cameraControlState.distance = cameraControlState.maxDistance;
|
|
}
|
|
}
|
|
}, { passive: false });
|
|
renderer.domElement.addEventListener("contextmenu", function(e) {
|
|
e.preventDefault();
|
|
});
|
|
window.addEventListener("keydown", function(e) {
|
|
if (e.code === "Space" && !e.repeat) {
|
|
e.preventDefault();
|
|
interactionMode = interactionMode === "auto" ? "manual" : "auto";
|
|
}
|
|
});
|
|
}
|
|
function processImage(imageUrl) {
|
|
if (!imageUrl || !sceneData) return;
|
|
const img = new Image();
|
|
img.crossOrigin = "anonymous";
|
|
img.src = imageUrl;
|
|
img.onload = function() {
|
|
const canvas = document.createElement("canvas");
|
|
const ctx = canvas.getContext("2d");
|
|
const resolution = 200;
|
|
const aspect = img.width / img.height;
|
|
let drawWidth = aspect > 1 ? resolution : resolution * aspect;
|
|
let drawHeight = aspect > 1 ? resolution / aspect : resolution;
|
|
canvas.width = resolution; canvas.height = resolution;
|
|
ctx.fillStyle = "black"; ctx.fillRect(0, 0, resolution, resolution);
|
|
ctx.drawImage(img, (resolution - drawWidth) / 2, (resolution - drawHeight) / 2, drawWidth, drawHeight);
|
|
const imgData = ctx.getImageData(0, 0, resolution, resolution).data;
|
|
const validPoints = [];
|
|
for (let y = 0; y < resolution; y++) {
|
|
for (let x = 0; x < resolution; x++) {
|
|
const idx = (y * resolution + x) * 4;
|
|
const r = imgData[idx], g = imgData[idx + 1], b = imgData[idx + 2];
|
|
if ((r + g + b) / 3 > 15) validPoints.push({ pos: [(x / resolution - 0.5) * 38 * SCENE_SCALE, (0.5 - y / resolution) * 38 * SCENE_SCALE, ((r + g + b) / 765 - 0.5) * 12 * SCENE_SCALE], col: [r / 255, g / 255, b / 255] });
|
|
}
|
|
}
|
|
if (validPoints.length > 0) {
|
|
const { targetPositions, targetColors, geometry } = sceneData;
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
const i3 = i * 3, point = validPoints[i % validPoints.length];
|
|
targetPositions[i3] = point.pos[0] + (Math.random() - 0.5) * 0.4 * SCENE_SCALE;
|
|
targetPositions[i3 + 1] = point.pos[1] + (Math.random() - 0.5) * 0.4 * SCENE_SCALE;
|
|
targetPositions[i3 + 2] = point.pos[2] + (Math.random() - 0.5) * 1.5 * SCENE_SCALE;
|
|
targetColors[i3] = point.col[0]; targetColors[i3 + 1] = point.col[1]; targetColors[i3 + 2] = point.col[2];
|
|
}
|
|
geometry.attributes.targetPosition.needsUpdate = true;
|
|
geometry.attributes.targetColor.needsUpdate = true;
|
|
}
|
|
};
|
|
}
|
|
function generateShape(shapeName) {
|
|
if (!sceneData) return;
|
|
const { targetPositions, targetColors, geometry } = sceneData;
|
|
const points = [];
|
|
const shapeGreen = new THREE.Color(0x00ff66);
|
|
const shapeWhite = new THREE.Color(0xffffff);
|
|
switch (shapeName) {
|
|
case "heart": for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 2; const u = Math.random() * Math.PI * 2; const v = Math.random() * Math.PI; const scale = 18; const x = scale * (16 * Math.pow(Math.sin(t), 3)) / 16 * Math.sin(v) * Math.cos(u); const y = scale * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)) / 16; const z = scale * (16 * Math.pow(Math.sin(t), 3)) / 16 * Math.sin(v) * Math.sin(u) * 0.8; const spread = 3.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "butterfly": for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 12; const scale = 5.5; const exp = Math.exp(Math.cos(t)) - 2 * Math.cos(4 * t) - Math.pow(Math.sin(t / 12), 5); const x = Math.sin(t) * exp * scale; const y = Math.cos(t) * exp * scale; const z = (Math.random() - 0.5) * 15; const spread = 2.5; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z], col: [color.r, color.g, color.b] }); } break;
|
|
case "rose": for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 14; const k = 5; const r = Math.cos(k * t) * 15; const h = (Math.random() - 0.5) * 20; const x = r * Math.cos(t); const y = h; const z = r * Math.sin(t); const spread = 2.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "cube": const cubeSize = 18; for (let i = 0; i < PARTICLE_COUNT; i++) { const face = Math.floor(Math.random() * 6); let x, y, z; const a = (Math.random() - 0.5) * cubeSize; const b = (Math.random() - 0.5) * cubeSize; switch (face) { case 0: x = cubeSize / 2; y = a; z = b; break; case 1: x = -cubeSize / 2; y = a; z = b; break; case 2: x = a; y = cubeSize / 2; z = b; break; case 3: x = a; y = -cubeSize / 2; z = b; break; case 4: x = a; y = b; z = cubeSize / 2; break; case 5: x = a; y = b; z = -cubeSize / 2; break; } const spread = 2.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "pyramid": const pyrHeight = 25; const pyrBase = 20; for (let i = 0; i < PARTICLE_COUNT; i++) { const onBase = Math.random() < 0.3; let x, y, z; if (onBase) { x = (Math.random() - 0.5) * pyrBase; z = (Math.random() - 0.5) * pyrBase; y = -pyrHeight / 2; } else { const t = Math.random(); const baseX = (Math.random() - 0.5) * pyrBase * (1 - t); const baseZ = (Math.random() - 0.5) * pyrBase * (1 - t); x = baseX; y = -pyrHeight / 2 + t * pyrHeight; z = baseZ; } const spread = 2.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "spiral": for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 10; const r = 10 + Math.sin(t * 3) * 4; const x = r * Math.cos(t); const y = (i / PARTICLE_COUNT - 0.5) * 40; const z = r * Math.sin(t); const spread = 2.5; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "star": for (let i = 0; i < PARTICLE_COUNT; i++) { const angle = Math.random() * Math.PI * 2; const starPoints = 5; const innerR = 8; const outerR = 18; const pointAngle = Math.floor(angle / (Math.PI * 2 / starPoints)) * (Math.PI * 2 / starPoints); const t = (angle - pointAngle) / (Math.PI / starPoints); let r; if (t < 1) { r = outerR - (outerR - innerR) * t; } else { r = innerR + (outerR - innerR) * (t - 1); } const x = r * Math.cos(angle); const z = r * Math.sin(angle); const y = (Math.random() - 0.5) * 15; const spread = 2.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "sphere": for (let i = 0; i < PARTICLE_COUNT; i++) { const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const r = 16; const x = r * Math.sin(phi) * Math.cos(theta); const y = r * Math.sin(phi) * Math.sin(theta); const z = r * Math.cos(phi); const spread = 2.5; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "dna": for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 8; const y = (i / PARTICLE_COUNT - 0.5) * 45; const r = 10; const strand = i % 3; let x, z; if (strand === 0) { x = r * Math.cos(t); z = r * Math.sin(t); } else if (strand === 1) { x = r * Math.cos(t + Math.PI); z = r * Math.sin(t + Math.PI); } else { const barPos = Math.random(); x = r * Math.cos(t) * (1 - barPos) + r * Math.cos(t + Math.PI) * barPos; z = r * Math.sin(t) * (1 - barPos) + r * Math.sin(t + Math.PI) * barPos; } const spread = 2.0; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + (Math.random() - 0.5) * spread, y + (Math.random() - 0.5) * spread, z + (Math.random() - 0.5) * spread], col: [color.r, color.g, color.b] }); } break;
|
|
case "infinity": const infScale = 25; for (let i = 0; i < PARTICLE_COUNT; i++) { const t = (i / PARTICLE_COUNT) * Math.PI * 6; const denom = 1 + Math.sin(t) * Math.sin(t); let x = infScale * Math.cos(t) / denom; let y = infScale * Math.sin(t) * Math.cos(t) / denom; const verticalSpread = (Math.random() - 0.5) * 20; const z = (Math.random() - 0.5) * 15 + Math.sin(t * 2) * 5; const thickness = 4.0; const offsetX = (Math.random() - 0.5) * thickness; const offsetY = (Math.random() - 0.5) * thickness + verticalSpread * 0.3; const offsetZ = (Math.random() - 0.5) * thickness; const color = Math.random() > 0.7 ? shapeGreen : shapeWhite; points.push({ pos: [x + offsetX, y + offsetY, z + offsetZ], col: [color.r, color.g, color.b] }); } break;
|
|
}
|
|
if (points.length > 0) {
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) { const i3 = i * 3; const point = points[i % points.length]; targetPositions[i3] = point.pos[0] * SCENE_SCALE; targetPositions[i3 + 1] = point.pos[1] * SCENE_SCALE; targetPositions[i3 + 2] = point.pos[2] * SCENE_SCALE; targetColors[i3] = point.col[0]; targetColors[i3 + 1] = point.col[1]; targetColors[i3 + 2] = point.col[2]; }
|
|
geometry.attributes.targetPosition.needsUpdate = true;
|
|
geometry.attributes.targetColor.needsUpdate = true;
|
|
uploadedImage = "shape";
|
|
}
|
|
}
|
|
document.addEventListener("DOMContentLoaded", initParticleCanvas);
|
|
</script>
|
|
</body>
|
|
</html>
|