Giant refactor. added layers. ui overhaul. added save/load and we now got presets
This commit is contained in:
Sam
2025-12-28 03:21:25 +13:00
parent f01076df57
commit 14ec23237f
90 changed files with 4971 additions and 22901 deletions

24
Tests/react (rename)/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,12 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

View File

@@ -0,0 +1,33 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

4160
Tests/react (rename)/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
{
"name": "react",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^10.0.7",
"@react-three/fiber": "^9.1.2",
"leva": "^0.10.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"three": "^0.176.0"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"vite": "^6.3.1"
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,21 @@
/* Remove default App.css styles if they interfere */
/* You might want to keep some or none depending on desired layout */
/* Example: Remove padding and max-width from #root if set here */
/* #root {
max-width: 100%;
padding: 0;
} */
/* Remove logo styles etc. */
/* .logo { ... } */
/* .logo:hover { ... } */
/* ... etc ... */
/* @keyframes logo-spin { ... } */
/* @media (prefers-reduced-motion: no-preference) { ... } */
/* .card { ... } */
/* .read-the-docs { ... } */

View File

@@ -0,0 +1,59 @@
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { useControls, Leva } from 'leva' // Import leva
import PhyllotaxisSystem from './PhyllotaxisSystem' // Import Phyllotaxis system
// import PolyTwistSystem from './PolyTwistSystem' // Import PolyTwist system
import './App.css'
// Define animation options
const animationOptions = {
Phyllotaxis: 'Phyllotaxis',
PolyTwist: 'PolyTwist',
// Add more animation names here as you implement them
}
function App() {
// Leva controls for animation selection and global settings
const { selectedAnimation, speedMultiplier } = useControls({
selectedAnimation: {
options: animationOptions,
value: animationOptions.Phyllotaxis, // Default selection
label: 'Animation',
},
speedMultiplier: {
value: 1.0, // Global speed multiplier
min: 0.01,
max: 5.0,
step: 0.01,
label: 'Global Speed',
},
})
// Function to render the selected animation component
const renderSelectedAnimation = () => {
switch (selectedAnimation) {
case animationOptions.Phyllotaxis:
return <PhyllotaxisSystem speedMultiplier={speedMultiplier} />
// case animationOptions.PolyTwist:
// return <PolyTwistSystem speedMultiplier={speedMultiplier} />
// Add cases for other animations
default:
return null
}
}
return (
<>
{/* Leva panel will be added here automatically */}
<Leva /> {/* Add the Leva panel, initially collapsed */}
<Canvas camera={{ position: [0, 0, 500], fov: 75 }}> {/* Adjusted camera Z */}
<ambientLight intensity={0.8} /> {/* Increased ambient light */}
<pointLight position={[100, 100, 100]} intensity={1.5} /> {/* Adjusted point light */}
{renderSelectedAnimation()} {/* Render the chosen animation */}
<OrbitControls />
</Canvas>
</>
)
}
export default App

View File

@@ -0,0 +1,130 @@
import React, { useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { useControls, folder } from 'leva'; // Import leva controls
// Default values matching original config
const DEFAULTS = {
width: 24,
nMax: 300,
color1: '#2D81FC',
color2: '#FC0362',
pointSize: 5.0, // Added point size control
};
function PhyllotaxisSystem({ speedMultiplier }) { // Accept global speed multiplier
const pointsRef = useRef();
const positionBufferRef = useRef();
const colorBufferRef = useRef();
// Leva controls specific to Phyllotaxis
const { width, nMax, color1, color2, pointSize } = useControls('Phyllotaxis', {
// Group controls in a folder
width: { value: DEFAULTS.width, min: 1, max: 50, step: 1, label: 'Width Factor' },
nMax: { value: DEFAULTS.nMax, min: 10, max: 5000, step: 10, label: 'Particle Count' },
pointSize: { value: DEFAULTS.pointSize, min: 0.1, max: 20, step: 0.1, label: 'Point Size' },
Colors: folder({ // Sub-folder for colors
color1: { value: DEFAULTS.color1, label: 'Color 1' },
color2: { value: DEFAULTS.color2, label: 'Color 2' },
})
});
// Memoize particle data generation, recalculate if controls change
const particles = useMemo(() => {
const positions = new Float32Array(nMax * 3);
const colors = new Float32Array(nMax * 3);
const tempColor = new THREE.Color();
const col1 = new THREE.Color(color1);
const col2 = new THREE.Color(color2);
for (let i = 0; i < nMax; i++) {
const n = i;
const radius = width * Math.sqrt(n);
const angle = 0; // Initial angle
positions[i * 3] = radius * Math.cos(angle);
positions[i * 3 + 1] = radius * Math.sin(angle);
positions[i * 3 + 2] = 0;
tempColor.copy(col1).lerp(col2, n / nMax);
colors[i * 3] = tempColor.r;
colors[i * 3 + 1] = tempColor.g;
colors[i * 3 + 2] = tempColor.b;
}
return { positions, colors };
// Dependencies: recalculate when these control values change
}, [nMax, width, color1, color2]);
// Update positions in the animation loop
useFrame((state) => {
if (!positionBufferRef.current || !colorBufferRef.current) return;
// Use combined speed (global * local if needed, here just global)
const time = state.clock.elapsedTime * speedMultiplier * 0.3; // Adjusted speed factor
const positions = positionBufferRef.current.array;
for (let i = 0; i < nMax; i++) {
const n = i;
const radius = width * Math.sqrt(n);
const angle = n * time; // Angle driven by time and index
positions[i * 3] = radius * Math.cos(angle);
positions[i * 3 + 1] = radius * Math.sin(angle);
// z remains 0
}
positionBufferRef.current.needsUpdate = true;
// Update point size if material exists
if (pointsRef.current && pointsRef.current.material) {
pointsRef.current.material.size = pointSize;
}
});
// Effect to update buffers when particles data changes (due to control changes)
React.useEffect(() => {
if (positionBufferRef.current) {
positionBufferRef.current.array = particles.positions;
positionBufferRef.current.needsUpdate = true;
}
if (colorBufferRef.current) {
colorBufferRef.current.array = particles.colors;
colorBufferRef.current.needsUpdate = true;
}
}, [particles]);
return (
<points ref={pointsRef}>
<bufferGeometry>
{/* Use keys to force re-creation if nMax changes drastically */}
<bufferAttribute
key={`pos-${nMax}`}
ref={positionBufferRef}
attach="attributes-position"
count={particles.positions.length / 3} // Use actual length
array={particles.positions}
itemSize={3}
usage={THREE.DynamicDrawUsage} // Mark as dynamic
/>
<bufferAttribute
key={`col-${nMax}`}
ref={colorBufferRef}
attach="attributes-color"
count={particles.colors.length / 3} // Use actual length
array={particles.colors}
itemSize={3}
usage={THREE.DynamicDrawUsage} // Mark as dynamic
/>
</bufferGeometry>
<pointsMaterial
size={pointSize}
vertexColors={true}
sizeAttenuation={true}
depthWrite={false} // Often good for particles
blending={THREE.AdditiveBlending} // Example blending mode
/>
</points>
);
}
export default PhyllotaxisSystem;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,44 @@
// filepath: c:\Users\samkl\Documents\GitHub\animate\docs\react\src\filters.js
import * as THREE from 'three'; // Might be needed for future filters
// Additive filter application function
export function applyFilters(baseValue, filters = [], time) {
let offset = 0;
filters.forEach(filter => {
switch (filter.type) {
case 'sin':
// Ensure defaults if properties are missing
offset += Math.sin(time * (filter.frequency ?? 1)) * (filter.amplitude ?? 0);
break;
// --- Add Noise Filter ---
case 'noise':
// Simple pseudo-random noise using time and an offset
// Use a combination of sin functions for a smoother noise
const noiseVal = (Math.sin(time * (filter.frequency ?? 1) * 1.3 + filter.id * 10) + Math.sin(time * (filter.frequency ?? 1) * 2.7 + filter.id * 5)) / 2;
offset += noiseVal * (filter.amplitude ?? 0);
break;
// --- End Noise Filter ---
default:
console.warn(`Unknown filter type: ${filter.type}`);
}
});
return baseValue + offset;
}
// Function to get default parameters for a filter type
export function getDefaultFilterParams(type) {
switch (type) {
case 'sin':
return { amplitude: 10, frequency: 1 };
case 'noise': // Default for noise
return { amplitude: 5, frequency: 1 }; // Added frequency for noise control
default:
return {};
}
}
// Available filter types (for dropdowns)
export const FILTER_TYPES = {
SIN: 'sin',
NOISE: 'noise',
};

View File

@@ -0,0 +1,87 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #000000;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
/* Remove display flex and place-items */
/* display: flex; */
/* place-items: center; */
min-width: 320px;
min-height: 100vh;
overflow: hidden; /* Prevent scrollbars from body */
}
/* Make root and canvas full screen */
#root {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
/* Remove text-align center if you don't want leva centered initially */
/* text-align: center; */
}
canvas {
display: block; /* Ensure canvas takes up block space */
width: 100%;
height: 100%;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,8 @@
import React from 'react' // Use React instead of StrictMode for now
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<App />
)

View File

@@ -0,0 +1,138 @@
import { useState, useMemo, useCallback } from 'react';
import { button, buttonGroup, folder, monitor } from 'leva';
import { applyFilters, getDefaultFilterParams, FILTER_TYPES } from './filters';
/**
* Custom hook to manage a numeric control value with additive filters in Leva.
*
* @param {string} key - A unique key for this control (used in Leva schema).
* @param {number} initialValue - The initial base value.
* @param {object} options - Leva control options (min, max, step, label, etc.).
* @returns {[Function, object]} A tuple containing:
* - A function `getFilteredValue(time)` that returns the value with filters applied for the given time.
* - A Leva schema fragment for this control and its filters.
*/
export function useFilteredControl(key, initialValue, options = {}) {
const [baseValue, setBaseValue] = useState(initialValue);
const [filters, setFilters] = useState([]);
const label = options.label || key;
// --- Filter Management Functions ---
const addFilter = useCallback((type) => {
const newFilter = {
id: Date.now() + Math.random(),
type: type,
...getDefaultFilterParams(type),
};
setFilters((prev) => [...prev, newFilter]);
}, []);
const removeFilter = useCallback((id) => {
setFilters((prev) => prev.filter((f) => f.id !== id));
}, []);
const updateFilter = useCallback((id, newParams) => {
setFilters((prev) =>
prev.map((f) => (f.id === id ? { ...f, ...newParams } : f))
);
}, []);
const clearFilters = useCallback(() => {
setFilters([]);
}, []);
// --- Leva Schema Generation ---
const schema = useMemo(() => {
const controlSchema = {};
// 1. Base Value Slider
controlSchema[key] = {
...options,
value: baseValue,
label: `Base ${label}`,
onChange: setBaseValue,
};
// 2. Filters Folder
const filterControls = {};
// Prefix keys with the control key to ensure uniqueness
filterControls[`${key}_Add Filter`] = buttonGroup({
// Dynamically create buttons for available filter types
...Object.entries(FILTER_TYPES).reduce((acc, [name, type]) => {
acc[name] = () => addFilter(type);
return acc;
}, {}),
});
filterControls[`${key}_Applied`] = monitor(filters.length, { graph: false });
filterControls[`${key}_Clear`] = button(clearFilters, {
label: 'Clear All',
disabled: filters.length === 0
});
// 3. Individual Filter Controls
filters.forEach((filter, index) => {
const filterKey = `${key}_filter_${filter.id}`;
const filterFolderContent = {};
filterFolderContent[`type_${filter.id}`] = {
value: filter.type,
options: Object.values(FILTER_TYPES),
label: 'Type',
onChange: (newType) => updateFilter(filter.id, { type: newType, ...getDefaultFilterParams(newType) }),
};
// Common Amplitude Control (adjust range based on param if needed)
filterFolderContent[`amp_${filter.id}`] = {
value: filter.amplitude,
min: 0,
max: options.max ? options.max * 0.5 : 50, // Example: Max amplitude is half of base max
step: (options.max ? options.max * 0.5 : 50) / 100, // Example step
label: 'Amplitude',
onChange: (v) => updateFilter(filter.id, { amplitude: v }),
};
// Common Frequency Control
filterFolderContent[`freq_${filter.id}`] = {
value: filter.frequency,
min: 0.1,
max: 10,
step: 0.1,
label: 'Frequency',
onChange: (v) => updateFilter(filter.id, { frequency: v }),
};
filterFolderContent[`remove_${filter.id}`] = button(() => removeFilter(filter.id), { label: 'Remove' });
// Add the folder for this specific filter
filterControls[filterKey] = folder(filterFolderContent, {
collapsed: true,
label: `${label} Filter ${index + 1} (${filter.type})`,
});
});
// Add the main filters folder under the base control's key
controlSchema[`${label} Filters`] = folder(filterControls);
return controlSchema;
}, [key, label, options, baseValue, filters, addFilter, removeFilter, updateFilter, clearFilters]);
// --- Filtered Value Calculation ---
const getFilteredValue = useCallback((time) => {
// Ensure baseValue is treated as a number
const numericBaseValue = Number(baseValue);
if (isNaN(numericBaseValue)) {
console.warn(`Base value for ${key} is not a number:`, baseValue);
return 0; // Or initialValue
}
const filtered = applyFilters(numericBaseValue, filters, time);
// Optional: Clamp the filtered value to the original min/max if desired
if (options.min !== undefined && options.max !== undefined) {
return Math.max(options.min, Math.min(filtered, options.max));
}
return filtered;
}, [baseValue, filters, key, options.min, options.max]);
return [getFilteredValue, schema];
}

View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

185
Tests/tomp4_test/fresh.html Normal file
View File

@@ -0,0 +1,185 @@
<!-- <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.8.1/dist/ffmpeg.min.js"></script> -->
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.10.0/dist/ffmpeg.min.js"></script>
<canvas id="canvas" style="height: 900px; width: 900px"></canvas>
<video id="myVideo" controls="controls"></video>
<video id="output-video" controls="controls"></video>
<a id="dl" href="" download="download.mp4"></a>
<div id="message"></div>
<script>
let c = document.getElementById("canvas");
let ctx = c.getContext("2d");
// ctx.canvas.width = window.innerWidth;
// ctx.canvas.height = window.innerHeight;
ctx.canvas.width = 900;
ctx.canvas.height = 900;
centerX = ctx.canvas.width / 2;
centerY = ctx.canvas.height / 2;
let deg_per_sec = 30 / 5000;
let timeLimit = 10;
let fps = 60;
rotation = 0; //was = j = angle
currentFrame = 0; //was = i
const { createFFmpeg } = FFmpeg;
const ffmpeg = createFFmpeg({
log: true,
});
const transcode = async (webcamData) => {
const message = document.getElementById("message");
const name = "record.webm";
await ffmpeg.load();
message.innerHTML = "Start transcoding";
await ffmpeg.write(name, webcamData);
await ffmpeg.transcode(name, "output.mp4");
message.innerHTML = "Complete transcoding";
const data = ffmpeg.read("output.mp4");
const video = document.getElementById("output-video");
video.src = URL.createObjectURL(
new Blob([data.buffer], { type: "video/mp4" })
);
dl.href = video.src;
dl.innerHTML = "download mp4";
};
fn().then(async ({ url, blob }) => {
transcode(new Uint8Array(await blob.arrayBuffer()));
});
function fn() {
var recordedChunks = [];
var time = 0;
var canvas = document.getElementById("canvas");
return new Promise(function (res, rej) {
var stream = canvas.captureStream(60);
mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm; codecs=vp9",
});
mediaRecorder.start(time);
mediaRecorder.ondataavailable = function (e) {
recordedChunks.push(event.data);
// for demo, removed stop() call to capture more than one frame
};
mediaRecorder.onstop = function (event) {
var blob = new Blob(recordedChunks, {
type: "video/webm",
});
var url = URL.createObjectURL(blob);
res({ url, blob }); // resolve both blob and url in an object
myVideo.src = url;
// removed data url conversion for brevity
};
// for demo, draw random lines and then stop recording
// var i = 0,
// tid = setInterval(() => {
// if (currentFrame > timeLimit / (1 / fps)) {
// // draw 20 lines
// clearInterval(tid);
// mediaRecorder.stop();
// }
// // let canvas = document.querySelector("canvas");
// // let cx = canvas.getContext("2d");
// // cx.beginPath();
// // cx.strokeStyle = "green";
// // cx.moveTo(Math.random() * 600, Math.random() * 600);
// // cx.lineTo(Math.random() * 600, Math.random() * 600);
// // cx.stroke();
// render_clear();
// // Draw_Phyllotaxis(rotation + 3.1);
// Draw_Phyllotaxis(rotation + 1.5);
// rotation += deg_per_sec / fps; // was = j = angle, now = rotation
// currentFrame += 1; // was = i
// }, 1000 / fps);
function render() {
if (currentFrame < timeLimit / (1 / fps)) {
setTimeout(() => {
render();
render_clear();
// Draw_Phyllotaxis(rotation + 3.1);
Draw_Phyllotaxis(rotation + 1.5);
console.log(rotation + 3.1);
// Draw_Phyllotaxis(rotation/5000);
// Draw_center(); //Debugging
// DrawBorder();
}, 1000 / fps);
rotation += deg_per_sec / fps; // was = j = angle, now = rotation
currentFrame += 1; // was = i
}
else{
mediaRecorder.stop();
}
}
render();
});
}
function Draw_Phyllotaxis(angle) {
// colour1 = [255, 170, 0];
// colour2 = [255, 0, 221];
colour1 = [45, 129, 252];
colour2 = [252, 3, 98];
var c = 24; //something to do with width. but not width
for (let n = 0; n < 300; n += 1) {
ncolour = LerpRGB(colour1, colour2, Math.cos(rad(n / 2)));
var a = n * angle; //137.5;
var r = c * Math.sqrt(n);
var x = r * Math.cos(a) + centerX;
var y = r * Math.sin(a) + centerY;
ctx.beginPath();
ctx.arc(x, y, 8, 0, 2 * Math.PI);
ctx.fillStyle = colourToText(ncolour);
ctx.fill();
}
}
function rad(degrees) {
var pi = Math.PI;
return degrees * (pi / 180);
}
function LerpRGB(a, b, t) {
if (t < 0) {
t *= -1;
}
var newColor = [0, 0, 0];
newColor[0] = a[0] + (b[0] - a[0]) * t;
newColor[1] = a[1] + (b[1] - a[1]) * t;
newColor[2] = a[2] + (b[2] - a[2]) * t;
return newColor;
}
function colourToText(colour) {
return "rgb(" + colour[0] + "," + colour[1] + "," + colour[2] + ")";
}
function render_clear() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
</script>

View File

@@ -0,0 +1,177 @@
<!DOCTYPE html>
<html>
<!-- ahhh -->
<body style="margin: 0; overflow: hidden">
<canvas
id="myCanvas"
width="10"
height="10"
style="display: block; box-sizing: border-box"
>
Your browser does not support the HTML5 canvas tag.</canvas
>
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.10.0/dist/ffmpeg.min.js"></script>
<script>
const { createFFmpeg } = FFmpeg;
const ffmpeg = createFFmpeg({
log: true,
});
const transcode = async (webcamData) => {
const message = document.getElementById("message");
const name = "record.webm";
await ffmpeg.load();
message.innerHTML = "Start transcoding";
await ffmpeg.write(name, webcamData);
await ffmpeg.transcode(name, "output.mp4");
message.innerHTML = "Complete transcoding";
const data = ffmpeg.read("output.mp4");
const video = document.getElementById("output-video");
video.src = URL.createObjectURL(
new Blob([data.buffer], { type: "video/mp4" })
);
dl.href = video.src;
dl.innerHTML = "download mp4";
};
fn().then(async ({ url, blob }) => {
transcode(new Uint8Array(await blob.arrayBuffer()));
});
function fn() {
var recordedChunks = [];
var time = 0;
var canvas = document.getElementById("canvas");
return new Promise(function (res, rej) {
var stream = canvas.captureStream(60);
mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm; codecs=vp9",
});
mediaRecorder.start(time);
mediaRecorder.ondataavailable = function (e) {
recordedChunks.push(event.data);
// for demo, removed stop() call to capture more than one frame
};
mediaRecorder.onstop = function (event) {
var blob = new Blob(recordedChunks, {
type: "video/webm",
});
var url = URL.createObjectURL(blob);
res({ url, blob }); // resolve both blob and url in an object
myVideo.src = url;
// removed data url conversion for brevity
};
// for demo, draw random lines and then stop recording
var i = 0,
tid = setInterval(() => {
if (i++ > 20) {
// draw 20 lines
clearInterval(tid);
mediaRecorder.stop();
}
let canvas = document.querySelector("canvas");
let cx = canvas.getContext("2d");
cx.beginPath();
cx.strokeStyle = "green";
cx.moveTo(Math.random() * 100, Math.random() * 100);
cx.lineTo(Math.random() * 100, Math.random() * 100);
cx.stroke();
}, 200);
});
}
let c = document.getElementById("myCanvas");
let ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
let deg_per_sec = 30 / 5000;
let time = 0.1;
let fps = 60;
centerX = ctx.canvas.width / 2;
centerY = ctx.canvas.height / 2;
rotation = 0; //was = j = angle
currentFrame = 0; //was = i
function render() {
if (currentFrame < time / (1 / fps)) {
setTimeout(() => {
render();
render_clear();
// Draw_Phyllotaxis(rotation + 3.1);
Draw_Phyllotaxis(rotation + 1.5);
console.log(rotation + 3.1);
// Draw_Phyllotaxis(rotation/5000);
// Draw_center(); //Debugging
// DrawBorder();
}, 1000 / fps);
rotation += deg_per_sec / fps; // was = j = angle, now = rotation
currentFrame += 1; // was = i
}
}
render();
function Draw_Phyllotaxis(angle) {
// colour1 = [255, 170, 0];
// colour2 = [255, 0, 221];
colour1 = [45, 129, 252];
colour2 = [252, 3, 98];
var c = 24; //something to do with width. but not width
for (let n = 0; n < 300; n += 1) {
ncolour = LerpRGB(colour1, colour2, Math.cos(rad(n / 2)));
var a = n * angle; //137.5;
var r = c * Math.sqrt(n);
var x = r * Math.cos(a) + centerX;
var y = r * Math.sin(a) + centerY;
ctx.beginPath();
ctx.arc(x, y, 8, 0, 2 * Math.PI);
ctx.fillStyle = colourToText(ncolour);
ctx.fill();
}
}
function rad(degrees) {
var pi = Math.PI;
return degrees * (pi / 180);
}
function LerpRGB(a, b, t) {
if (t < 0) {
t *= -1;
}
var newColor = [0, 0, 0];
newColor[0] = a[0] + (b[0] - a[0]) * t;
newColor[1] = a[1] + (b[1] - a[1]) * t;
newColor[2] = a[2] + (b[2] - a[2]) * t;
return newColor;
}
function colourToText(colour) {
return "rgb(" + colour[0] + "," + colour[1] + "," + colour[2] + ")";
}
function render_clear() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Template</title>
<style>
/* Set canvas to occupy the full width and height of the window */
canvas {
display: block;
width: 800px;
height: 800px;
margin: 0 560px;
padding: 0;
}
.canvas2d {
position: absolute !important;
top: 0px;
left: 0px;
}
body{
margin:0
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<canvas id="canvas2d" class="canvas2d"></canvas>
<script src="index.js">
</script>
</body>
</html>

194
Tests/webGl/New/index.js Normal file
View File

@@ -0,0 +1,194 @@
// Get the canvas element and create a WebGL context
const canvas = document.getElementById('canvas');
const canvas2d = document.getElementById('canvas2d');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
let ctx = canvas2d.getContext("2d");
ctx.canvas.width = 800;
ctx.canvas.height = 800;
// Check if WebGL is available
if (!gl) {
alert('WebGL not supported in your browser.');
} else {
// Set the canvas size and WebGL viewport
canvas.width = 800;
canvas.height = 800;
let centerX = ctx.canvas.width / 2;
let centerY = ctx.canvas.height / 2;
gl.viewport(0, 0, canvas.width, canvas.height);
// Define the vertex shader source code
const glsl = x => x;
const vertexShaderSource = glsl`
attribute vec2 position;
uniform float u_rotation;
uniform vec2 u_pos;
void main() {
float centerX = 0.0;
float centerY = 0.0;
float xPos = position.x;
float yPos = position.y;
float x = xPos * cos(u_rotation) - yPos * sin(u_rotation);
float y = yPos * cos(u_rotation) + xPos * sin(u_rotation);
gl_Position = vec4(x+u_pos.x , y+u_pos.y , 0, 1);
}
`;
// Define the fragment shader source code
const fragmentShaderSource = `
precision mediump float;
uniform vec4 fColor;
void main() {
gl_FragColor = fColor;
}
`;
// Create and compile the vertex and fragment shaders
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// Create a program, attach the shaders, and link the program
const program = createProgram(gl, vertexShader, fragmentShader);
// Get the attribute and uniform locations
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
const positionLocalAttributeLocation = gl.getUniformLocation(program, 'u_pos');
const rotationUniformLocation = gl.getUniformLocation(program, 'u_rotation');
const fColorLocation = gl.getUniformLocation(program, "fColor");
// Create a buffer to store vertex positions
const positionBuffer = gl.createBuffer();
function polyPoints(sides) {
let pointsArr = [];
let width = 1;
let rotation = 0
pointsArr.push(width * Math.cos((rotation * Math.PI) / 180));
pointsArr.push(width * Math.sin((rotation * Math.PI) / 180))
for (let i = 0; i < sides; i++) {
pointsArr.push(width * Math.cos((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180))
pointsArr.push(width * Math.sin((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180))
}
return pointsArr
}
function rad(degrees) {
var pi = Math.PI;
return degrees * (pi / 180);
}
// Function to create and compile a shader
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
}
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// Function to create a program and link shaders
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
return program;
}
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// Function to draw the scene
let prevTime = 0
function drawScene(time) {
// Convert time to seconds
// console.log(time - prevTime)
prevTime = time
time *= 0.001;
// Clear the canvas with a black background
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Use the shader program
gl.useProgram(program);
// Enable the position attribute
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// Create a buffer to store vertex positions
let blue = [0.17, 0.49, 0.85] //blue
let green = [0.17, 0.85, 0.46] //green
let pink = [0.96, 0.24, 0.86] //green
render_clear();
for (let i = 0; i < 10000; i++) {
DrawPolygon(4, 400, (0+time)*i*0.1, "Crimson")
// drawPoly(4, time * i * 0.001, blue, 0, 0)
}
Draw_center()
drawCircle()
// console.log(time)
// Request the next frame to be drawn
requestAnimationFrame(drawScene);
}
const positions1 = polyPoints(4)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions1), gl.STATIC_DRAW);
function drawPoly(sides, speed, colour, x, y) {
gl.uniform1f(rotationUniformLocation, speed);
gl.uniform4f(fColorLocation, colour[0], colour[1], colour[2], 1);
gl.uniform2f(positionLocalAttributeLocation, x, y);
gl.drawArrays(gl.LINE_LOOP, 0, positions1.length / 2);
}
// Start rendering the scene
requestAnimationFrame(drawScene);
function Draw_center() {
ctx.beginPath();
ctx.moveTo(centerX - 400, centerY);
ctx.lineTo(centerX + 400, centerY);
ctx.moveTo(centerX, centerY - 400);
ctx.lineTo(centerX, centerY + 400);
ctx.strokeStyle = "green";
ctx.stroke();
// console.log("drawn center")
}
function drawCircle() {
ctx.beginPath();
ctx.arc(centerX, centerY, 400, 0, 2 * Math.PI, false);
ctx.strokeStyle = "green";
ctx.stroke();
}
function DrawPolygon(sides, width, rotation, colour) {
ctx.beginPath();
ctx.moveTo(
centerX + width * Math.cos((rotation * Math.PI) / 180),
centerY + width * Math.sin((rotation * Math.PI) / 180)
);
for (var i = 1; i <= sides; i += 1) {
ctx.lineTo(
centerX + width * Math.cos((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180),
centerY + width * Math.sin((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180)
);
}
ctx.strokeStyle = colour;
ctx.lineWidth = 1;
ctx.stroke();
}
function render_clear() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "rgb(0,0,0,0)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
}

View File

@@ -0,0 +1,112 @@
body {
-webkit-box-sizing: border-box;
/* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box;
/* Firefox, other Gecko */
box-sizing: border-box;
/* Opera/IE 8+ */
height: 100%
}
p {
margin: 0px;
}
canvas {
position: absolute;
}
#toolbar {
display: flex;
flex-flow: column;
height: 100%;
position: absolute;
padding: 0px 20px 0px 20px;
width: 500px;
height: 100vh;
overflow-y: scroll;
background-color: rgb(189, 189, 189);
}
#custom {
display: flex;
flex-flow: column;
height: 100%;
/* position: absolute; */
padding: 0px 20px 0px 20px;
/* width: 500px; */
/* height: 100vh; */
background-color: rgb(189, 189, 189);
}
.button {
display: block;
position: absolute;
right: 20px;
z-index: 100;
}
.controls {
display: flex;
margin: 8px 0px;
}
.controlFrameButton {
width: 8%;
}
.controlPauseButton {
width: 80%;
margin: auto;
}
/* <!-- HTML !-->
<button class="button-8" role="button">Button 8</button> */
/* CSS */
.button-8 {
background-color: #e1ecf4;
border-radius: 3px;
border: 1px solid #7aa7c7;
box-shadow: rgba(255, 255, 255, .7) 0 1px 0 0 inset;
box-sizing: border-box;
color: #1f3f55;
cursor: pointer;
display: inline-block;
/* font-family: -apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif; */
font-size: 13px;
font-weight: 400;
line-height: 1.15385;
/* margin: 0; */
outline: none;
padding: 8px .8em;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: baseline;
white-space: nowrap;
}
.button-8:hover,
.button-8:focus {
background-color: #b3d3ea;
color: #2c5777;
}
.button-8:focus {
box-shadow: 0 0 0 4px rgba(0, 149, 255, .15);
}
.button-8:active {
background-color: #a0c7e4;
box-shadow: none;
color: #2c5777;
}
.buttonReset{
background-color: #f4e1e1;
}

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<!-- ahhh -->
<head>
<title>Document</title>
<link rel="stylesheet" href="./css/styles.css">
</head>
<body style="margin:0;">
<canvas id="canvas" style="display: block;box-sizing: border-box;"></canvas>
<div id="toolbar">
<br>
<select id="shape-selector">
<option value="EyePrototype">EyePrototype</option>
<option value="Nodal_expanding">Nodal_expanding</option>
<option value="MaryFace">MaryFace</option>
<option value="CircleExpand">CircleExpand</option>
<option value="PolyTwistColourWidth">PolyTwistColourWidth</option>
<option value="FloralPhyllo">FloralPhyllo</option>
<option value="FloralPhyllo_Accident">FloralPhyllo_Accident</option>
<option value="Spiral1">Spiral1</option>
<option value="FloralAccident">FloralAccident</option>
<option value="Phyllotaxis">Phyllotaxis</option>
<option value="SquareTwist_angle">SquareTwist_angle</option>
</select>
<div id="custom"></div>
<br>
<p>Controls:</p>
<p>
Press "Space" to pause and start the animation
</p>
<p>
Press "P" to show/hide the control panel
</p>
<br>
<p>Speed</p>
<input type="text" id="inputDegPerSec" value="10" onchange="ChangeDegPerSec(this.value)">
<br>
<p>Controls</p>
<div class="controls">
<button class="controlFrameButton button-8" onclick="BackwardFrame()"><</button>
<button class="controlPauseButton button-8" id="pauseButton" onclick="TogglePause()">Play</button>
<button class="controlFrameButton button-8" onclick="ForwardFrame()">></button>
</div>
<button class="buttonReset button-8" id="resetButton" onclick="Reset()">Reset Rotation</button>
</div>
<div>
<button onclick="manualToggleSettings()" class="button">Show/hide</button>
</div>
</body>
<script src="./js/helper.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="./js/math.js" type="text/javascript"></script>
<script src="./js/objects.js" type="text/javascript"></script>
<script src="./js/index.js"></script>
</html>

View File

@@ -0,0 +1,274 @@
async function fetchConfig(className) {
// const config = await $.getJSON("config.json");
const config = {
EyePrototype: [
{ type: "range", min: -20, max: 20, defaultValue: 0, property: "Val1" },
// { type: "color", defaultValue: "#00fffb", property: "colourPupil" },
],
};
return config[className];
}
function addControl(item, instance) {
console.log(item);
let parentDiv = document.getElementById("custom");
let title = document.createElement("p");
title.innerText = item.property + ": " + item.defaultValue;
title.id = "elText" + item.property;
let control = document.createElement("input");
control.type = item.type;
if (item.type === "range") {
control.min = item.min;
control.max = item.max;
}
control.value = item.defaultValue;
control.className = "control";
control.id = "el" + item.property;
const listener = (event) => {
const newValue = event.target.value;
instance[item.property] =
item.type === "range" ? parseInt(newValue, 10) : newValue;
title.innerText = item.property + ": " + newValue;
};
control.addEventListener("input", listener);
parentDiv.appendChild(title);
parentDiv.appendChild(control);
return { element: control, listener };
}
function drawEyelid(width, x1, y1, colour) {
x1 -= centerX;
y1 -= centerY;
const angle = Math.atan2(y1, x1);
const cosAngle = Math.cos(angle);
const sinAngle = Math.sin(angle);
const x2 = cosAngle * width;
const y2 = sinAngle * width;
const x3Old = width / 2;
const y3Old = width / 2;
const x4Old = width / 2;
const y4Old = -width / 2;
const x3 = x3Old * cosAngle - y3Old * sinAngle;
const y3 = x3Old * sinAngle + y3Old * cosAngle;
const x4 = x4Old * cosAngle - y4Old * sinAngle;
const y4 = x4Old * sinAngle + y4Old * cosAngle;
x1 += centerX;
y1 += centerY;
const x2Final = x2 + x1;
const y2Final = y2 + y1;
const x3Final = x3 + x1;
const y3Final = y3 + y1;
const x4Final = x4 + x1;
const y4Final = y4 + y1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x3Final, y3Final, x2Final, y2Final);
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x4Final, y4Final, x2Final, y2Final);
ctx.fillStyle = colour;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.stroke();
}
function drawEyelidAccident(x1, y1) {
let leafWidth = 120;
let leafHeight = 60;
x1 -= centerX;
y1 -= centerY;
let angle = Math.atan(y1 / x1);
// if(angle >=Math.PI){
// angle -=Math.PI
// console.log("greater called")
// }
angle = Math.abs(angle);
let x2Old = 0 + leafWidth;
let y2Old = 0;
let x3Old = 0 + leafWidth / 2;
let y3Old = 0 + leafHeight / 2;
let x4Old = 0 + leafWidth / 2;
let y4Old = 0 - leafHeight / 2;
let x2 = x2Old * Math.cos(angle) - y2Old * Math.sin(angle);
let y2 = x2Old * Math.sin(angle) + y2Old * Math.cos(angle);
let x3 = x3Old * Math.cos(angle) - y3Old * Math.sin(angle);
let y3 = x3Old * Math.sin(angle) + y3Old * Math.cos(angle);
let x4 = x4Old * Math.cos(angle) - y4Old * Math.sin(angle);
let y4 = x4Old * Math.sin(angle) + y4Old * Math.cos(angle);
let oldx1 = x1;
let oldy1 = y1;
x1 += centerX; // +x2/2
y1 += centerY; // +x2/2
x2 += x1;
y2 += y1;
x3 += x1;
y3 += y1;
x4 += x1;
y4 += y1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x3, y3, x2, y2);
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x4, y4, x2, y2);
ctx.fillStyle = "black";
ctx.fill();
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x3, y3, x2, y2);
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(x4, y4, x2, y2);
ctx.strokeStyle = "orange";
ctx.stroke();
}
function DrawPolygon(sides, width, rotation, colour, line_width) {
ctx.beginPath();
ctx.moveTo(
centerX + width * Math.cos((rotation * Math.PI) / 180),
centerY + width * Math.sin((rotation * Math.PI) / 180)
);
for (var i = 1; i <= sides; i += 1) {
ctx.lineTo(
centerX +
width *
Math.cos((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180),
centerY +
width * Math.sin((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180)
);
}
ctx.strokeStyle = colour;
ctx.lineWidth = line_width;
ctx.stroke();
}
function rad(degrees) {
return (degrees * Math.PI) / 180;
}
function colourToText(colour) {
return "rgb(" + colour[0] + "," + colour[1] + "," + colour[2] + ")";
}
function waveNormal(x, max) {
let val = Math.sin((x / max) * Math.PI * 2 - max * (Math.PI / (max * 2))) / 2 + 0.5
return val
}
function LerpHex(a, b, amount) {
var ah = parseInt(a.replace(/#/g, ""), 16),
ar = ah >> 16,
ag = (ah >> 8) & 0xff,
ab = ah & 0xff,
bh = parseInt(b.replace(/#/g, ""), 16),
br = bh >> 16,
bg = (bh >> 8) & 0xff,
bb = bh & 0xff,
rr = ar + amount * (br - ar),
rg = ag + amount * (bg - ag),
rb = ab + amount * (bb - ab);
return (
"#" + (((1 << 24) + (rr << 16) + (rg << 8) + rb) | 0).toString(16).slice(1)
);
}
function LerpRGB(a, b, t) {
if (t < 0) {
t *= -1;
}
var newColor = [0, 0, 0];
newColor[0] = a[0] + (b[0] - a[0]) * t;
newColor[1] = a[1] + (b[1] - a[1]) * t;
newColor[2] = a[2] + (b[2] - a[2]) * t;
return newColor;
}
function lerpRGB(a, b, t) {
const result = [0, 0, 0];
for (let i = 0; i < 3; i++) {
result[i] = (1 - t) * a[i] + t * b[i];
}
return result;
}
function drawCenter(width) {
console.log("center?")
ctx.strokeStyle = "pink";
ctx.lineWidth = 1
ctx.beginPath();
ctx.moveTo(centerX - width, centerY);
ctx.lineTo(centerX + width, centerY);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(centerX, centerY - width);
ctx.lineTo(centerX, centerY + width);
ctx.closePath();
ctx.stroke();
}
function render_clear() {
// ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// ctx.fillStyle = "black";
// ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
function rotatePointTmp(x, y, centerXX, centerYY, rotation) {
let xFromC = x - centerXX;
let yFromC = y - centerYY;
let d = (xFromC ** 2 + yFromC ** 2) ** 0.5
// let orgAngle = Math.atan2(yFromC/xFromC)
let orgAngle = Math.atan2(xFromC, yFromC)
let tmp = Math.cos(rad(orgAngle - rotation)) * d
// console.log(Math.cos((-90)*(Math.PI/180)))
console.log(orgAngle)
console.log(rad(rotation))
console.log(Math.cos(orgAngle - rad(rotation)) * d)
console.log(d)
// console.log(d)
let newPointX = Math.cos(orgAngle - rad(rotation+90)) * d + centerXX;
let newPointY = Math.sin(orgAngle - rad(rotation+90)) * d + centerYY;
return [newPointX, newPointY]
}
function rotatePoint(x,y,rotation){
let nCos = Math.cos(rad(rotation))
// console.log(nCos*(180/Math.PI))
// console.log(rad(rotation))
let nSin = Math.sin(rad(rotation))
let newX = x*nCos - y*nSin
let newY = y*nCos + x*nSin
return [newX,newY]
}

View File

@@ -0,0 +1,148 @@
//jshint esversion:8
let c = document.getElementById("canvas");
const gl = canvas.getContext('webgl');
// canvas.width = 800;
// canvas.height = 800;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
// let ctx = c.getContext("2d");
// ctx.canvas.width = window.innerWidth;
// ctx.canvas.height = window.innerHeight;
// centerX = ctx.canvas.width / 2;
// centerY = ctx.canvas.height / 2;
window.addEventListener('resize', function () {
console.log('addEventListener - resize');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}, true);
let deg_per_sec = 10;
let targetFps = 60;
let frameDuration = 1000 / targetFps;
let rotation = 0; //was = j = angle
let paused = true;
render_clear();
let drawObj = null;
function createInstance(className, args) {
const classMap = {
EyePrototype: EyePrototype,
// Add more class constructors here as needed
};
if (classMap.hasOwnProperty(className)) {
return new classMap[className](...args);
} else {
throw new Error(`Unknown class name: ${className}`);
}
}
async function updateDrawObj() {
const shapeSelector = document.getElementById("shape-selector");
const selectedShape = shapeSelector.value;
const config = await fetchConfig(selectedShape);
if (drawObj) {
drawObj.remove(); // Remove the previous instance
}
// Extract default values from the configuration
const defaultValues = config
// .filter((item) => item.type !== "color") // Exclude color inputs
.map((item) => item.defaultValue);
drawObj = createInstance(selectedShape, defaultValues);
// drawObj = await createShapeWithRandomProperties(813311281, config1);
console.log(drawObj)
drawObj.initialise(config);
}
updateDrawObj();
function render() {
setTimeout(() => {
requestAnimationFrame(() => {
render_clear();
if (drawObj) {
drawObj.draw(rotation);
}
if (!paused) {
rotation += deg_per_sec / targetFps;
}
// drawCenter(300)
});
render();
}, frameDuration);
}
document
.getElementById("shape-selector")
.addEventListener("change", updateDrawObj);
let toolbarShowing = true;
document.addEventListener("keydown", toggleSettings);
function manualToggleSettings() {
console.log("hi")
toolbarShowing = !toolbarShowing;
let tb = document.getElementById("toolbar");
if (toolbarShowing) {
tb.style.display = "flex";
} else {
tb.style.display = "none";
}
}
function toggleSettings(e) {
if (e.key == "p") {
toolbarShowing = !toolbarShowing;
}
if (e.code === "Space") {
paused = !paused;
}
let tb = document.getElementById("toolbar");
if (toolbarShowing) {
tb.style.display = "flex";
} else {
tb.style.display = "none";
}
}
function TogglePause() {
let pb = document.getElementById("pauseButton");
paused = !paused;
if (paused) {
pb.textContent = "Play";
} else {
pb.textContent = "Pause";
}
}
function Reset() {
rotation = 0; //was = j = angle
currentFrame = 0;
}
function ForwardFrame() {
rotation += deg_per_sec / fps; // was = j = innerRotation, now = rotation
currentFrame += 1; // was = i
}
function BackwardFrame() {
rotation -= deg_per_sec / fps; // was = j = innerRotation, now = rotation
currentFrame -= 1; // was = i
}
function ChangeDegPerSec(newValue) {
deg_per_sec = newValue;
}
window.onload = render;

View File

@@ -0,0 +1,77 @@
function rotateMatrix2d(p, angle) {
// cos0 sin0
// -sin0 cos0
const angleD = rad(angle);
const r = [
[Math.cos(angleD), Math.sin(angleD)],
[-Math.sin(angleD), Math.cos(angleD)],
];
const newPoint = [
p[0] * r[0][0] + p[1] * r[0][1],
p[0] * r[1][0] + p[1] * r[1][1],
];
return newPoint;
}
function rotateMatrix3dX(p, angle) {
// cos0 sin0
// -sin0 cos0
const angleD = rad(angle);
const r = [
[1, 0, 0],
[0, Math.cos(angleD), -Math.sin(angleD)],
[0, Math.sin(angleD), Math.cos(angleD)],
];
const newPoint = [
p[0] * r[0][0] + p[1] * r[0][1] + p[2] * r[0][2],
p[0] * r[1][0] + p[1] * r[1][1] + p[2] * r[1][2],
p[0] * r[2][0] + p[1] * r[2][1] + p[2] * r[2][2],
];
return newPoint;
}
function rotateMatrix3dY(p, angle) {
// cos0 sin0
// -sin0 cos0
const angleD = rad(angle);
const r = [
[Math.cos(angleD), 0, Math.sin(angleD)],
[0, 1, 0],
[-Math.sin(angleD), 0, Math.cos(angleD)],
];
const newPoint = [
p[0] * r[0][0] + p[1] * r[0][1] + p[2] * r[0][2],
p[0] * r[1][0] + p[1] * r[1][1] + p[2] * r[1][2],
p[0] * r[2][0] + p[1] * r[2][1] + p[2] * r[2][2],
];
return newPoint;
}
function rotateMatrix3dZ(p, angle) {
// cos0 sin0
// -sin0 cos0
const angleD = rad(angle);
const r = [
[Math.cos(angleD), -Math.sin(angleD), 0],
[Math.sin(angleD), Math.cos(angleD), 0],
[0, 0, 1],
];
const newPoint = [
p[0] * r[0][0] + p[1] * r[0][1] + p[2] * r[0][2],
p[0] * r[1][0] + p[1] * r[1][1] + p[2] * r[1][2],
p[0] * r[2][0] + p[1] * r[2][1] + p[2] * r[2][2],
];
return newPoint;
}
function projectionOrth(v) {
const p = [
[1, 0, 0],
[0, 1, 0],
];
const nPoint = [
p[0][0] * v[0] + p[0][1] * v[1] + p[0][2] * v[2],
p[1][0] * v[0] + p[1][1] * v[1] + p[1][2] * v[2],
];
return nPoint;
}

View File

@@ -0,0 +1,153 @@
class BaseShape {
constructor() {
this.controls = []; // Keep track of created elements and event listeners
this.speedMultiplier = 100;
}
initialise(config) {
for (let item of config) {
const { element, listener } = addControl(item, this);
this.controls.push({ element, listener });
}
const { element, listener } = addControl({ type: "range", min: 1, max: 500, defaultValue: 100, property: "speedMultiplier", }, this);
this.controls.push({ element, listener });
}
remove() {
this.controls.forEach(({ element, listener }) => {
if (element && listener) {
element.removeEventListener("input", listener);
}
if (element && element.parentElement) {
element.parentElement.removeChild(element);
const titleElement = document.getElementById("elText" + element.id.slice(2));
titleElement.parentElement.removeChild(titleElement);
}
});
this.controls = [];
}
draw() {
throw new Error("Draw function not implemented");
}
}
class EyePrototype extends BaseShape {
constructor(Val1) {
super();
this.Val1 = Val1;
this.iVal1Location = "";
this.iResolutionLocation = ""//gl.getUniformLocation(this.program, 'iResolution');
this.program = gl.createProgram();
}
initialise(config) {
for (let item of config) {
const { element, listener } = addControl(item, this);
this.controls.push({ element, listener });
}
const { element, listener } = addControl({ type: "range", min: 1, max: 500, defaultValue: 100, property: "speedMultiplier", }, this);
this.controls.push({ element, listener });
const vertexShaderSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
// Create fragment shader using the code you wrote
const fragmentShaderSource = `
precision mediump float;
uniform vec2 iResolution;
uniform float iTime;
uniform float iVal1;
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
vec2 uv0 = uv;
vec3 colour = vec3(1.0, 1.5, 2.0);
vec3 colour2 = vec3(0.2, 0.2, 1.0);
float pi = 3.1415926;
float val1Div = iVal1/10.0;
for (float i = 0.0; i < 2.0; i++) {
uv = fract(uv * val1Div) - 0.5;
float distance = length(uv);
distance = sin(distance * pi * 5.0 - iTime) / (pi * 5.0);
distance = abs(distance);
distance = 0.02 / distance;
colour = mix(colour, colour2, length(uv));
colour *= distance;
}
float distance = length(uv0);
distance = sin(distance * pi * 5.0 - iTime) / (pi * 5.0);
distance = abs(distance);
distance = 0.02 / distance;
colour = mix(colour, colour2, length(uv0));
colour *= distance;
fragColor = vec4(colour, 1.0);
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 iResolution = vec2(${canvas.width.toFixed(1)}, ${canvas.height.toFixed(1)});
float iTime = ${performance.now().toFixed(3)} / 1000.0;
vec4 fragColor;
mainImage(fragColor, fragCoord);
gl_FragColor = fragColor;
}
`;
// Compile and link shaders
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// const this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
gl.useProgram(this.program);
// Create vertex buffer
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(this.program, 'position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// Set uniforms
this.iResolutionLocation = gl.getUniformLocation(this.program, 'iResolution');
this.iVal1Location = gl.getUniformLocation(this.program, 'iVal1');
}
draw(rotation) {
gl.uniform2f(this.iResolutionLocation, canvas.width, canvas.height);
gl.uniform1f(gl.getUniformLocation(this.program, 'iTime'), performance.now() / 1000.0);
gl.uniform1f(this.iVal1Location, this.Val1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// requestAnimationFrame(render);
}
}

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL Centered Square</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,98 @@
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL is not supported in your browser.');
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const vertices = [
-0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5,
];
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
const vertexShaderSource = `
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_scale;
void main() {
vec2 scaledPosition = a_position * u_scale;
vec2 position = scaledPosition + u_translation;
vec2 zeroToOne = position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
const translationUniformLocation = gl.getUniformLocation(program, 'u_translation');
const scaleUniformLocation = gl.getUniformLocation(program, 'u_scale');
function drawScene() {
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
const squareWidth = 50;
const squareHeight = 50;
const xCenter = (canvas.width - squareWidth) / 2;
const yCenter = (canvas.height - squareHeight) / 2;
gl.uniform2f(translationUniformLocation, xCenter, yCenter);
gl.uniform2f(scaleUniformLocation, squareWidth, squareHeight);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
requestAnimationFrame(drawScene);
}
drawScene();

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Phyllotaxis Animation with Three.js and ShaderMaterial</title>
<style>body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> -->
<script src="/index.975ef6c8.js" defer=""></script>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Phyllotaxis Animation with Three.js and ShaderMaterial</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> -->
<script src="./src/index.js" type="module"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "my-threejs-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "parcel index.html",
"build": "parcel build index.html"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"parcel": "^2.12.0"
},
"dependencies": {
"@parcel/transformer-glsl": "^2.12.0",
"postprocessing": "^6.35.3",
"three": "^0.163.0"
}
}

View File

@@ -0,0 +1,19 @@
varying vec3 vColor;
void main() {
float distance = length(gl_PointCoord - vec2(0.5, 0.5));
float radius = 0.5; // The radius of the circle, where 0.5 is the edge of the point sprite
float intensity = 0.5; // Gaussian for smooth intensity increase
// float intensity = exp(-pow(distance * 3.0, 2.0)); // Gaussian for smooth intensity increase
// If the distance is greater than the radius, discard the fragment (make it transparent)
if (distance > radius) {
discard;
}
// Use the intensity to modulate the color within the circle
vec3 brightColor = vColor * intensity;
brightColor = clamp(brightColor, 0.0, 2.5);
gl_FragColor = vec4(brightColor, 1.0); // Full alpha within the circle
}

View File

@@ -0,0 +1,33 @@
uniform float time;
uniform vec3 color1;
uniform vec3 color2;
varying vec3 vColor;
const float PI = 3.1415926535897932384626433832795;
const float goldenAngle = PI * (3.0 - sqrt(5.0));
void main() {
// Calculate the normalized distance from center
float index = float(gl_VertexID);
float radiuss = sqrt(index) * 5.0;
float normalizedRadius = radiuss / 200.0; // Adjust this divisor based on your pattern's size
// Calculate the angle for each point based on its index
float angle = index * (goldenAngle + time * 0.001);
if (mod(index,2.0) != 0.0){
angle *=-1.0;
}
// Color changes over time - Adding a slow wave effect based on the distance from center
float colorTime = time * -2.1; // Slow down the color change over time
float colorWave = sin(normalizedRadius * PI * 2.0 + colorTime); // Wave based on distance and time
float colorMixFactor = colorWave * 0.5 + 0.5; // Normalize the wave between 0 and 1
vColor = mix(color1, color2, colorMixFactor); // Mix the colors based on the computed factor
// Calculate the animated position
vec3 animatedPosition = vec3(radiuss * cos(angle), radiuss * sin(angle), 0.0);
gl_Position = projectionMatrix * modelViewMatrix * vec4(animatedPosition, 1.0);
gl_PointSize = 10.0;
}

View File

@@ -0,0 +1,110 @@
import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import vertexShader from '../shaders/vertex.glsl';
import fragmentShader from '../shaders/fragment.glsl';
// Now you can use `vertexShader` and `fragmentShader` as strings in your Three.js material
// Basic setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setClearColor(0x000000, 0); // Transparent background color
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene.add(renderer.domElement);
// Phyllotaxis setup
const pointsGeometry = new THREE.BufferGeometry();
const n_points = 5000;
const positions = new Float32Array(n_points * 3);
const goldenAngle = Math.PI * (3 - Math.sqrt(5));
// const goldenAngle = 0;
for (let i = 0; i < n_points; i++) {
const angle = i * goldenAngle;
const r = Math.sqrt(i) * 5;
positions[i * 3] = r * Math.cos(angle); // x
positions[i * 3 + 1] = r * Math.sin(angle); // y
positions[i * 3 + 2] = 0; // z
}
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// ShaderMaterial
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
color1: { value: new THREE.Color(0x2D81FC) }, // Blue
color2: { value: new THREE.Color(0xFC0362) }, // Pink
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
blending: THREE.AdditiveBlending,
});
const points = new THREE.Points(pointsGeometry, shaderMaterial);
// Assign the points mesh to the bloom layer
scene.add(points);
points.layers.enable(1);
camera.position.z = 200;
// Compile the shader and catch any errors
renderer.compile(scene, camera);
// Create layers
const bloomLayer = new THREE.Layers();
bloomLayer.set(1); // Setting the bloom layer to layer 1
// Set up the bloom pass
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.0, 0.4, 0.85);
bloomPass.threshold = 0.1
bloomPass.strength = 2.5
bloomPass.radius = 0.55
bloomPass.renderToScreen = true
// Create a render scene pass
const renderScene = new RenderPass(scene, camera);
// Set up the effect composer
const composer = new EffectComposer(renderer);
composer.setSize(window.innerWidth, window.innerHeight);
composer.addPass(renderScene);
composer.addPass(bloomPass);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
// renderer.toneMappingExposure = Math.pow( 0.9, 4.0 )
renderer.toneMappingExposure = 1.0
renderer.autoClear = false;
points.layers.set(1);
// Animation loop
function animate() {
requestAnimationFrame(animate);
shaderMaterial.uniforms.time.value = performance.now() / 5000;
renderer.clear();
camera.layers.set(1);
composer.render();
renderer.clearDepth();
camera.layers.set(0);
renderer.render(scene, camera);
}
animate();
// Handle window resize
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Template</title>
<style>
/* Set canvas to occupy the full width and height of the window */
canvas {
display: block;
width: 800px;
height: 800px;
margin: 0 560px;
padding: 0;
}
.canvas2d {
position: absolute !important;
top: 0px;
left: 0px;
}
body{
margin:0
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<canvas id="canvas2d" class="canvas2d"></canvas>
<script src="index.js">
</script>
</body>
</html>

View File

@@ -0,0 +1,204 @@
// Get the canvas element and create a WebGL context
const canvas = document.getElementById('canvas');
const canvas2d = document.getElementById('canvas2d');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
let ctx = canvas2d.getContext("2d");
ctx.canvas.width = 800;
ctx.canvas.height = 800;
// Check if WebGL is available
if (!gl) {
alert('WebGL not supported in your browser.');
} else {
// Set the canvas size and WebGL viewport
canvas.width = 800;
canvas.height = 800;
let centerX = ctx.canvas.width / 2;
let centerY = ctx.canvas.height / 2;
gl.viewport(0, 0, canvas.width, canvas.height);
// Define the vertex shader source code
const vertexShaderSource = `
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
attribute vec2 position;
uniform float u_rotation;
uniform vec2 u_pos;
varying vec4 vColor;
void main() {
vColor = vec4(hsv2rgb(vec3(u_rotation, 0.5+pow(position.y+0.4, 10.0), 1.0)), 1.0);
float centerX = 0.0;
float centerY = 0.0;
float xPos = position.x;
float yPos = position.y;
float rot_cos = cos(u_rotation);
float rot_sin = sin(u_rotation);
float x = xPos * rot_cos - yPos * rot_sin;
float y = yPos * rot_cos + xPos * rot_sin;
gl_Position = vec4(x+u_pos.x , y+u_pos.y , 0, 1);
}
`;
// Define the fragment shader source code
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
uniform vec4 fColor;
void main() {
gl_FragColor = vColor;
}
`;
// Create and compile the vertex and fragment shaders
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// Create a program, attach the shaders, and link the program
const program = createProgram(gl, vertexShader, fragmentShader);
// Get the attribute and uniform locations
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
// const positionAttributeLocation1 = gl.getAttribLocation(program, 'u_pos');
const rotationUniformLocation = gl.getUniformLocation(program, 'u_rotation');
const fColorLocation = gl.getUniformLocation(program, "fColor");
// Create a buffer to store vertex positions
const positionBuffer = gl.createBuffer();
// Bind the buffer and send the vertex positions to the GPU
function polyPoints(sides) {
let pointsArr = [];
let width = 1;
let rotation = 0
pointsArr.push(width * Math.cos((rotation * Math.PI) / 180));
pointsArr.push(width * Math.sin((rotation * Math.PI) / 180))
for (let i = 0; i < sides; i++) {
pointsArr.push(width * Math.cos((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180))
pointsArr.push(width * Math.sin((i * 2 * Math.PI) / sides + (rotation * Math.PI) / 180))
}
return pointsArr
}
function rad(degrees) {
var pi = Math.PI;
return degrees * (pi / 180);
}
// Function to create and compile a shader
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
}
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// Function to create a program and link shaders
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
return program;
}
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// Function to draw the scene
let prevTime = 0
function drawScene(time) {
// Convert time to seconds
// console.log(time - prevTime)
prevTime = time
time *= 0.001;
// Clear the canvas with a black background
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Use the shader program
gl.useProgram(program);
// Enable the position attribute
// Create a buffer to store vertex positions
let blue = [0.17, 0.49, 0.85] //blue
let green = [0.17, 0.85, 0.46] //green
let pink = [0.96, 0.24, 0.86] //green
// drawPoly(3, time * 1, blue, 0, 0)
// drawPoly(3, time * 0.8, pink, 0.35, 0.35)
// drawPoly(5,time*0.6)
// drawPoly(6,time*0.4)
// drawPoly(7,time*0.2)
//
// prepare the gl state for the draw (can be here as drawPoly gets called multiple times with the same state)
// This could be improved by using a VAO to get the gpu to run over the for loop for you
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
for (let i = 0; i < 100; i++) {
drawPoly(4, time * i * 0.001, blue, 0, 0)
}
Draw_center()
drawCircle()
// console.log(time)
// Request the next frame to be drawn
requestAnimationFrame(drawScene);
}
function drawPoly(sides, speed, colour, x, y) {
gl.uniform1f(rotationUniformLocation, speed);
gl.uniform4f(fColorLocation, colour[0], colour[1], colour[2], 1);
gl.uniform2f(gl.getUniformLocation(program, 'u_pos'), x, y);
const positions = polyPoints(sides)
// const positionBuffer1 = gl.createBuffer();
// Assume the buffers are already bound
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
gl.drawArrays(gl.LINE_LOOP, 0, positions.length / 2);
}
// Start rendering the scene
requestAnimationFrame(drawScene);
function Draw_center() {
ctx.beginPath();
ctx.moveTo(centerX - 400, centerY);
ctx.lineTo(centerX + 400, centerY);
ctx.moveTo(centerX, centerY - 400);
ctx.lineTo(centerX, centerY + 400);
ctx.strokeStyle = "green";
ctx.stroke();
// console.log("drawn center")
}
function drawCircle() {
ctx.beginPath();
ctx.arc(centerX, centerY, 400, 0, 2 * Math.PI, false);
ctx.strokeStyle = "green";
ctx.stroke();
}
}