selkie 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/index.js +12 -4
- package/package.json +1 -4
- package/package_details.js +21 -0
- package/templates/build.js +268 -0
- package/templates/default.js +81 -0
- package/templates/glsl.js +251 -0
- package/templates/ml5-hand.js +367 -0
- package/templates/pressure.js +222 -0
- package/templates/svg.js +142 -0
- package/templates/templates.js +18 -1263
- package/templates/three.js +149 -0
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ selkie -t svg # Paper.js vector/SVG project
|
|
|
20
20
|
selkie -t three # Three.js 3D project
|
|
21
21
|
selkie -t glsl # WebGL shader project
|
|
22
22
|
selkie -t ml5-hand # ML5.js hand tracking project
|
|
23
|
+
selkie -t pressure # p5.js pressure drawing (iPad + Apple Pencil)
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
You'll be prompted for a folder name, then the project will be created with all dependencies installed.
|
package/index.js
CHANGED
|
@@ -6,17 +6,20 @@ import path from 'path';
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { exec, execSync } from 'child_process';
|
|
9
|
-
import { template as defaultTemplate, svgTemplate, threeTemplate, glslTemplate, ml5HandTrackingTemplate, buildScriptContent } from './templates/templates.js';
|
|
10
|
-
import { packageJsonContent, svgPackageJsonContent, threePackageJsonContent, glslPackageJsonContent, ml5HandTrackingPackageJsonContent } from './package_details.js';
|
|
9
|
+
import { template as defaultTemplate, svgTemplate, threeTemplate, glslTemplate, ml5HandTrackingTemplate, pressureTemplate, buildScriptContent } from './templates/templates.js';
|
|
10
|
+
import { packageJsonContent, svgPackageJsonContent, threePackageJsonContent, glslPackageJsonContent, ml5HandTrackingPackageJsonContent, pressurePackageJsonContent } from './package_details.js';
|
|
11
11
|
|
|
12
12
|
// Convert import.meta.url to a file path
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
|
|
16
|
+
// Read version from package.json so --version stays in sync with releases
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
18
|
+
|
|
16
19
|
// Initialize commander
|
|
17
20
|
const program = new Command();
|
|
18
21
|
program
|
|
19
|
-
.version(
|
|
22
|
+
.version(pkg.version)
|
|
20
23
|
.option('-t, --template <type>', 'specify the template type')
|
|
21
24
|
.parse(process.argv);
|
|
22
25
|
|
|
@@ -73,8 +76,13 @@ async function createProject() {
|
|
|
73
76
|
sketchContent = ml5HandTrackingTemplate.sketchContent;
|
|
74
77
|
randomContent = ml5HandTrackingTemplate.randomContent;
|
|
75
78
|
selectedPackageJson = ml5HandTrackingPackageJsonContent;
|
|
79
|
+
} else if (templateType === 'pressure') {
|
|
80
|
+
htmlContent = pressureTemplate.htmlContent;
|
|
81
|
+
sketchContent = pressureTemplate.sketchContent;
|
|
82
|
+
randomContent = pressureTemplate.randomContent;
|
|
83
|
+
selectedPackageJson = pressurePackageJsonContent;
|
|
76
84
|
} else {
|
|
77
|
-
throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand');
|
|
85
|
+
throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand, pressure');
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
// Create index.html
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selkie",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI for scaffolding generative art projects with p5.js, Paper.js, Three.js, GLSL, and ML5.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,8 +40,5 @@
|
|
|
40
40
|
"p5": "^1.9.4",
|
|
41
41
|
"paper": "^0.12.18",
|
|
42
42
|
"three": "^0.170.0"
|
|
43
|
-
},
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"live-server": "^1.2.2"
|
|
46
43
|
}
|
|
47
44
|
}
|
package/package_details.js
CHANGED
|
@@ -20,6 +20,27 @@ export const packageJsonContent = `{
|
|
|
20
20
|
"license": "ISC"
|
|
21
21
|
}`;
|
|
22
22
|
|
|
23
|
+
export const pressurePackageJsonContent = `{
|
|
24
|
+
"name": "project-name",
|
|
25
|
+
"version": "1.0.0",
|
|
26
|
+
"description": "Pressure-sensitive p5.js drawing (iPad + Apple Pencil)",
|
|
27
|
+
"main": "index.js",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"start": "live-server",
|
|
30
|
+
"build": "node build.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"p5": "^1.4.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"live-server": "^1.2.1",
|
|
37
|
+
"terser": "^5.31.0"
|
|
38
|
+
},
|
|
39
|
+
"keywords": ["generative", "p5.js", "apple-pencil", "pressure", "drawing"],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "ISC"
|
|
42
|
+
}`;
|
|
43
|
+
|
|
23
44
|
export const svgPackageJsonContent = `{
|
|
24
45
|
"name": "project-name",
|
|
25
46
|
"version": "1.0.0",
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Build script template - shared across all project types
|
|
2
|
+
export const buildScriptContent = `#!/usr/bin/env node
|
|
3
|
+
// build.js - Bundle project into single HTML file
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { minify } = require('terser');
|
|
7
|
+
|
|
8
|
+
async function build() {
|
|
9
|
+
console.log('Building single-file bundle...');
|
|
10
|
+
|
|
11
|
+
const templateType = detectTemplateType();
|
|
12
|
+
console.log('Detected template type:', templateType);
|
|
13
|
+
|
|
14
|
+
// Read source files
|
|
15
|
+
const html = fs.readFileSync('index.html', 'utf8');
|
|
16
|
+
const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
|
|
17
|
+
const random = fs.readFileSync('scripts/random.js', 'utf8');
|
|
18
|
+
|
|
19
|
+
// Read library based on template type
|
|
20
|
+
let library = '';
|
|
21
|
+
let libraryFile = '';
|
|
22
|
+
|
|
23
|
+
switch (templateType) {
|
|
24
|
+
case 'default':
|
|
25
|
+
libraryFile = 'scripts/p5.js';
|
|
26
|
+
if (fs.existsSync(libraryFile)) {
|
|
27
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
break;
|
|
30
|
+
case 'svg':
|
|
31
|
+
libraryFile = 'scripts/paper-full.js';
|
|
32
|
+
if (fs.existsSync(libraryFile)) {
|
|
33
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case 'three':
|
|
37
|
+
libraryFile = 'scripts/three.module.js';
|
|
38
|
+
if (fs.existsSync(libraryFile)) {
|
|
39
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'glsl':
|
|
43
|
+
// No library needed - uses native WebGL
|
|
44
|
+
break;
|
|
45
|
+
case 'ml5-hand':
|
|
46
|
+
console.warn('\\n⚠️ Warning: ML5 template requires internet for AI models.');
|
|
47
|
+
console.warn(' The bundled file will keep the CDN reference to ml5.js.\\n');
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Minify JavaScript
|
|
52
|
+
let minifiedLibrary = '';
|
|
53
|
+
if (library) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await minify(library, {
|
|
56
|
+
compress: true,
|
|
57
|
+
mangle: true,
|
|
58
|
+
format: { comments: false }
|
|
59
|
+
});
|
|
60
|
+
minifiedLibrary = result.code;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.warn('Could not minify library, using original:', e.message);
|
|
63
|
+
minifiedLibrary = library;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let minifiedSketch = sketch;
|
|
68
|
+
let minifiedRandom = random;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// For Three.js, transform ES6 imports before minifying
|
|
72
|
+
let sketchToMinify = sketch;
|
|
73
|
+
if (templateType === 'three') {
|
|
74
|
+
sketchToMinify = transformThreeJsSketch(sketch);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sketchResult = await minify(sketchToMinify, {
|
|
78
|
+
compress: true,
|
|
79
|
+
mangle: true,
|
|
80
|
+
format: { comments: false }
|
|
81
|
+
});
|
|
82
|
+
minifiedSketch = sketchResult.code;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn('Could not minify sketch.js, using original:', e.message);
|
|
85
|
+
if (templateType === 'three') {
|
|
86
|
+
minifiedSketch = transformThreeJsSketch(sketch);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const randomResult = await minify(random, {
|
|
92
|
+
compress: true,
|
|
93
|
+
mangle: true,
|
|
94
|
+
format: { comments: false }
|
|
95
|
+
});
|
|
96
|
+
minifiedRandom = randomResult.code;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.warn('Could not minify random.js, using original:', e.message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build the single HTML file
|
|
102
|
+
const bundledHtml = buildHtml(html, {
|
|
103
|
+
library: minifiedLibrary,
|
|
104
|
+
sketch: minifiedSketch,
|
|
105
|
+
random: minifiedRandom,
|
|
106
|
+
templateType
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Write output
|
|
110
|
+
fs.mkdirSync('dist', { recursive: true });
|
|
111
|
+
fs.writeFileSync('dist/bundle.html', bundledHtml);
|
|
112
|
+
|
|
113
|
+
const originalSize = Buffer.byteLength(html, 'utf8') +
|
|
114
|
+
Buffer.byteLength(sketch, 'utf8') +
|
|
115
|
+
Buffer.byteLength(random, 'utf8') +
|
|
116
|
+
Buffer.byteLength(library, 'utf8');
|
|
117
|
+
const bundleSize = fs.statSync('dist/bundle.html').size;
|
|
118
|
+
|
|
119
|
+
console.log('\\n✓ Bundle created: dist/bundle.html');
|
|
120
|
+
console.log(' Original size: ' + (originalSize / 1024).toFixed(1) + ' KB');
|
|
121
|
+
console.log(' Bundle size: ' + (bundleSize / 1024).toFixed(1) + ' KB');
|
|
122
|
+
console.log('\\nOpen dist/bundle.html in any browser - no server needed!');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function detectTemplateType() {
|
|
126
|
+
const html = fs.readFileSync('index.html', 'utf8');
|
|
127
|
+
if (html.includes('three.module.js') || html.includes('importmap')) return 'three';
|
|
128
|
+
if (html.includes('paper-full.js')) return 'svg';
|
|
129
|
+
if (html.includes('ml5')) return 'ml5-hand';
|
|
130
|
+
if (html.includes('p5.js')) return 'default';
|
|
131
|
+
// GLSL uses WebGL directly
|
|
132
|
+
if (html.includes('webgl') || html.includes('WebGL') || html.includes('gl.createShader')) return 'glsl';
|
|
133
|
+
// Check sketch.js for WebGL patterns
|
|
134
|
+
const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
|
|
135
|
+
if (sketch.includes('getContext(\\'webgl\\')') || sketch.includes('gl.createShader')) return 'glsl';
|
|
136
|
+
return 'default';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function transformThreeJsSketch(sketch) {
|
|
140
|
+
// Remove ES6 import statement
|
|
141
|
+
let transformed = sketch.replace(/import\\s+\\*\\s+as\\s+THREE\\s+from\\s+['\"]three['\"];?\\n?/g, '');
|
|
142
|
+
transformed = transformed.replace(/import\\s+\\{[^}]+\\}\\s+from\\s+['\"]three['\"];?\\n?/g, '');
|
|
143
|
+
return transformed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildHtml(originalHtml, options) {
|
|
147
|
+
const { library, sketch, random, templateType } = options;
|
|
148
|
+
|
|
149
|
+
// Extract the style content
|
|
150
|
+
const styleMatch = originalHtml.match(/<style>([\\s\\S]*?)<\\/style>/);
|
|
151
|
+
const styles = styleMatch ? styleMatch[1] : '';
|
|
152
|
+
|
|
153
|
+
// Extract title
|
|
154
|
+
const titleMatch = originalHtml.match(/<title>([^<]*)<\\/title>/);
|
|
155
|
+
const title = titleMatch ? titleMatch[1] : 'Generative Sketch';
|
|
156
|
+
|
|
157
|
+
// Build based on template type
|
|
158
|
+
if (templateType === 'three') {
|
|
159
|
+
// Three.js needs special handling - wrap library as IIFE
|
|
160
|
+
const wrappedLibrary = wrapThreeJsAsGlobal(library);
|
|
161
|
+
return \`<!DOCTYPE html>
|
|
162
|
+
<html>
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="utf-8">
|
|
165
|
+
<title>\${title}</title>
|
|
166
|
+
<style>\${styles}</style>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
<script>\${wrappedLibrary}</script>
|
|
170
|
+
<script>\${random}</script>
|
|
171
|
+
<script>\${sketch}</script>
|
|
172
|
+
</body>
|
|
173
|
+
</html>\`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (templateType === 'ml5-hand') {
|
|
177
|
+
// Keep CDN reference for ML5
|
|
178
|
+
return \`<!DOCTYPE html>
|
|
179
|
+
<html>
|
|
180
|
+
<head>
|
|
181
|
+
<meta charset="utf-8">
|
|
182
|
+
<title>\${title}</title>
|
|
183
|
+
<style>\${styles}</style>
|
|
184
|
+
</head>
|
|
185
|
+
<body>
|
|
186
|
+
<div id="container">
|
|
187
|
+
<video id="video" autoplay playsinline></video>
|
|
188
|
+
<canvas id="canvas"></canvas>
|
|
189
|
+
<div id="status">Initializing...</div>
|
|
190
|
+
</div>
|
|
191
|
+
<script src="https://unpkg.com/ml5@1/dist/ml5.min.js"></script>
|
|
192
|
+
<script>\${random}</script>
|
|
193
|
+
<script>\${sketch}</script>
|
|
194
|
+
</body>
|
|
195
|
+
</html>\`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (templateType === 'svg') {
|
|
199
|
+
// Paper.js template
|
|
200
|
+
return \`<!DOCTYPE html>
|
|
201
|
+
<html>
|
|
202
|
+
<head>
|
|
203
|
+
<meta charset="utf-8">
|
|
204
|
+
<title>\${title}</title>
|
|
205
|
+
<style>\${styles}</style>
|
|
206
|
+
</head>
|
|
207
|
+
<body>
|
|
208
|
+
<canvas id="canvas" resize></canvas>
|
|
209
|
+
<script>\${library}</script>
|
|
210
|
+
<script>\${random}</script>
|
|
211
|
+
<script>\${sketch}</script>
|
|
212
|
+
</body>
|
|
213
|
+
</html>\`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (templateType === 'glsl') {
|
|
217
|
+
// GLSL template - no library
|
|
218
|
+
return \`<!DOCTYPE html>
|
|
219
|
+
<html>
|
|
220
|
+
<head>
|
|
221
|
+
<meta charset="utf-8">
|
|
222
|
+
<title>\${title}</title>
|
|
223
|
+
<style>\${styles}</style>
|
|
224
|
+
</head>
|
|
225
|
+
<body>
|
|
226
|
+
<canvas id="canvas"></canvas>
|
|
227
|
+
<script>\${random}</script>
|
|
228
|
+
<script>\${sketch}</script>
|
|
229
|
+
</body>
|
|
230
|
+
</html>\`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Default p5.js template
|
|
234
|
+
return \`<!DOCTYPE html>
|
|
235
|
+
<html>
|
|
236
|
+
<head>
|
|
237
|
+
<meta charset="utf-8">
|
|
238
|
+
<title>\${title}</title>
|
|
239
|
+
<style>\${styles}</style>
|
|
240
|
+
</head>
|
|
241
|
+
<body>
|
|
242
|
+
<script>\${library}</script>
|
|
243
|
+
<script>\${random}</script>
|
|
244
|
+
<script>\${sketch}</script>
|
|
245
|
+
</body>
|
|
246
|
+
</html>\`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function wrapThreeJsAsGlobal(threeCode) {
|
|
250
|
+
// Wrap Three.js module code to expose THREE as global
|
|
251
|
+
// The three.module.js exports everything, we capture it
|
|
252
|
+
return \`(function() {
|
|
253
|
+
var exports = {};
|
|
254
|
+
var module = { exports: exports };
|
|
255
|
+
(function(exports, module) {
|
|
256
|
+
\${threeCode}
|
|
257
|
+
})(exports, module);
|
|
258
|
+
window.THREE = exports.THREE || exports;
|
|
259
|
+
// Also expose common exports directly
|
|
260
|
+
if (exports.Scene) window.THREE = exports;
|
|
261
|
+
})();\`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
build().catch(err => {
|
|
265
|
+
console.error('Build failed:', err);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
268
|
+
`;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const template = {
|
|
2
|
+
htmlContent:
|
|
3
|
+
`<!DOCTYPE html>
|
|
4
|
+
<html>
|
|
5
|
+
<head>
|
|
6
|
+
<title>p5.js Sketch</title>
|
|
7
|
+
<script src="scripts/p5.js"></script>
|
|
8
|
+
<script src="scripts/sketch.js"></script>
|
|
9
|
+
<script src="scripts/random.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
</body>
|
|
13
|
+
</html>`,
|
|
14
|
+
sketchContent: `function setup() {
|
|
15
|
+
createCanvas(400, 400);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function draw() {
|
|
19
|
+
background(220);
|
|
20
|
+
}`,
|
|
21
|
+
randomContent: `
|
|
22
|
+
// RANDOM Class ////////////////////////////////////////////////////////////
|
|
23
|
+
class Random {
|
|
24
|
+
constructor () {
|
|
25
|
+
this.useA = false;
|
|
26
|
+
let sfc32 = function (uint128Hex) {
|
|
27
|
+
let a = parseInt (uint128Hex.substr (0, 8), 16);
|
|
28
|
+
let b = parseInt (uint128Hex.substr (8, 8), 16);
|
|
29
|
+
let c = parseInt (uint128Hex.substr (16, 8), 16);
|
|
30
|
+
let d = parseInt (uint128Hex.substr (24, 8), 16);
|
|
31
|
+
return function () {
|
|
32
|
+
a |= 0;
|
|
33
|
+
b |= 0;
|
|
34
|
+
c |= 0;
|
|
35
|
+
d |= 0;
|
|
36
|
+
let t = (((a + b) | 0) + d) | 0;
|
|
37
|
+
d = (d + 1) | 0;
|
|
38
|
+
a = b ^ (b >>> 9);
|
|
39
|
+
b = (c + (c << 3)) | 0;
|
|
40
|
+
c = (c << 21) | (c >>> 11);
|
|
41
|
+
c = (c + t) | 0;
|
|
42
|
+
return (t >>> 0) / 4294967296;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
// seed prngA with first half of tokenData.hash
|
|
46
|
+
this.prngA = new sfc32 (tokenData.hash.substr (2, 32));
|
|
47
|
+
// seed prngB with second half of tokenData.hash
|
|
48
|
+
this.prngB = new sfc32 (tokenData.hash.substr (34, 32));
|
|
49
|
+
for (let i = 0; i < 1e6; i += 2) {
|
|
50
|
+
this.prngA ();
|
|
51
|
+
this.prngB ();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// random number between 0 (inclusive) and 1 (exclusive)
|
|
55
|
+
random_dec () {
|
|
56
|
+
this.useA = !this.useA;
|
|
57
|
+
return this.useA ? this.prngA () : this.prngB ();
|
|
58
|
+
}
|
|
59
|
+
// random number between a (inclusive) and b (exclusive)
|
|
60
|
+
random_num (a, b) {
|
|
61
|
+
return a + (b - a) * this.random_dec ();
|
|
62
|
+
}
|
|
63
|
+
// random integer between a (inclusive) and b (inclusive)
|
|
64
|
+
// requires a < b for proper probability distribution
|
|
65
|
+
random_int (a, b) {
|
|
66
|
+
return Math.floor (this.random_num (a, b + 1));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// random boolean with p as percent liklihood of true
|
|
70
|
+
random_bool (p) {
|
|
71
|
+
return this.random_dec () < p;
|
|
72
|
+
}
|
|
73
|
+
// random value in an array of items
|
|
74
|
+
random_choice (list) {
|
|
75
|
+
return list[this.random_int (0, list.length - 1)];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
`,
|
|
80
|
+
colorContent: `let a = ['CDCDCD', 'FF0000', 'FFA500', 'FFFF00', '008000', '0000FF', '4B0082', 'EE82EE'];`
|
|
81
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export const glslTemplate = {
|
|
2
|
+
htmlContent: `<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<title>GLSL Shader Sketch</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
background: #000;
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
canvas {
|
|
18
|
+
display: block;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
max-height: 100%;
|
|
21
|
+
}
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<canvas id="canvas"></canvas>
|
|
26
|
+
<script src="scripts/random.js"></script>
|
|
27
|
+
<script src="scripts/sketch.js"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>`,
|
|
30
|
+
sketchContent: `// WebGL setup
|
|
31
|
+
const canvas = document.getElementById('canvas');
|
|
32
|
+
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
33
|
+
|
|
34
|
+
if (!gl) {
|
|
35
|
+
alert('WebGL not supported');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Set canvas size
|
|
39
|
+
canvas.width = window.innerWidth;
|
|
40
|
+
canvas.height = window.innerHeight;
|
|
41
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
42
|
+
|
|
43
|
+
// Vertex shader source
|
|
44
|
+
const vertexShaderSource = \`
|
|
45
|
+
attribute vec2 a_position;
|
|
46
|
+
varying vec2 v_uv;
|
|
47
|
+
|
|
48
|
+
void main() {
|
|
49
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
50
|
+
v_uv = a_position * 0.5 + 0.5;
|
|
51
|
+
}
|
|
52
|
+
\`;
|
|
53
|
+
|
|
54
|
+
// Fragment shader source
|
|
55
|
+
const fragmentShaderSource = \`
|
|
56
|
+
precision mediump float;
|
|
57
|
+
uniform float u_time;
|
|
58
|
+
uniform vec2 u_resolution;
|
|
59
|
+
varying vec2 v_uv;
|
|
60
|
+
|
|
61
|
+
void main() {
|
|
62
|
+
vec2 uv = v_uv;
|
|
63
|
+
|
|
64
|
+
// Example: Animated gradient
|
|
65
|
+
vec3 color = vec3(
|
|
66
|
+
sin(uv.x * 3.14 + u_time) * 0.5 + 0.5,
|
|
67
|
+
sin(uv.y * 3.14 + u_time * 0.7) * 0.5 + 0.5,
|
|
68
|
+
sin((uv.x + uv.y) * 2.0 + u_time * 1.3) * 0.5 + 0.5
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
gl_FragColor = vec4(color, 1.0);
|
|
72
|
+
}
|
|
73
|
+
\`;
|
|
74
|
+
|
|
75
|
+
// Compile shader
|
|
76
|
+
function compileShader(source, type) {
|
|
77
|
+
const shader = gl.createShader(type);
|
|
78
|
+
gl.shaderSource(shader, source);
|
|
79
|
+
gl.compileShader(shader);
|
|
80
|
+
|
|
81
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
82
|
+
console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
|
|
83
|
+
gl.deleteShader(shader);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return shader;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create shader program
|
|
91
|
+
function createProgram(vertexSource, fragmentSource) {
|
|
92
|
+
const vertexShader = compileShader(vertexSource, gl.VERTEX_SHADER);
|
|
93
|
+
const fragmentShader = compileShader(fragmentSource, gl.FRAGMENT_SHADER);
|
|
94
|
+
|
|
95
|
+
if (!vertexShader || !fragmentShader) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const program = gl.createProgram();
|
|
100
|
+
gl.attachShader(program, vertexShader);
|
|
101
|
+
gl.attachShader(program, fragmentShader);
|
|
102
|
+
gl.linkProgram(program);
|
|
103
|
+
|
|
104
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
105
|
+
console.error('Program linking error:', gl.getProgramInfoLog(program));
|
|
106
|
+
gl.deleteProgram(program);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return program;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create full-screen quad
|
|
114
|
+
function createQuad() {
|
|
115
|
+
const positions = new Float32Array([
|
|
116
|
+
-1, -1,
|
|
117
|
+
1, -1,
|
|
118
|
+
-1, 1,
|
|
119
|
+
-1, 1,
|
|
120
|
+
1, -1,
|
|
121
|
+
1, 1,
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const buffer = gl.createBuffer();
|
|
125
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
126
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
|
127
|
+
return buffer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Initialize
|
|
131
|
+
const program = createProgram(vertexShaderSource, fragmentShaderSource);
|
|
132
|
+
const quadBuffer = createQuad();
|
|
133
|
+
|
|
134
|
+
if (!program) {
|
|
135
|
+
alert('Failed to create shader program');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get attribute and uniform locations
|
|
139
|
+
const positionLocation = gl.getAttribLocation(program, 'a_position');
|
|
140
|
+
const timeLocation = gl.getUniformLocation(program, 'u_time');
|
|
141
|
+
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
|
|
142
|
+
|
|
143
|
+
// Animation loop
|
|
144
|
+
let startTime = Date.now();
|
|
145
|
+
|
|
146
|
+
function animate() {
|
|
147
|
+
requestAnimationFrame(animate);
|
|
148
|
+
|
|
149
|
+
const currentTime = (Date.now() - startTime) / 1000.0;
|
|
150
|
+
|
|
151
|
+
// Clear
|
|
152
|
+
gl.clearColor(0, 0, 0, 1);
|
|
153
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
154
|
+
|
|
155
|
+
// Use program
|
|
156
|
+
gl.useProgram(program);
|
|
157
|
+
|
|
158
|
+
// Set uniforms
|
|
159
|
+
gl.uniform1f(timeLocation, currentTime);
|
|
160
|
+
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
|
|
161
|
+
|
|
162
|
+
// Set up quad
|
|
163
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
164
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
165
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
166
|
+
|
|
167
|
+
// Draw
|
|
168
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Handle window resize
|
|
172
|
+
window.addEventListener('resize', () => {
|
|
173
|
+
canvas.width = window.innerWidth;
|
|
174
|
+
canvas.height = window.innerHeight;
|
|
175
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Start animation
|
|
179
|
+
animate();
|
|
180
|
+
|
|
181
|
+
// Export PNG function - call this from console: exportPNG()
|
|
182
|
+
window.exportPNG = function(filename) {
|
|
183
|
+
filename = filename || 'sketch.png';
|
|
184
|
+
const link = document.createElement('a');
|
|
185
|
+
link.href = canvas.toDataURL('image/png');
|
|
186
|
+
link.download = filename;
|
|
187
|
+
link.click();
|
|
188
|
+
console.log('PNG exported as ' + filename);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Keyboard shortcut: Press 's' to save PNG
|
|
192
|
+
document.addEventListener('keydown', function(e) {
|
|
193
|
+
if (e.key === 's' && !e.ctrlKey && !e.metaKey) {
|
|
194
|
+
window.exportPNG();
|
|
195
|
+
}
|
|
196
|
+
});`,
|
|
197
|
+
randomContent: `// RANDOM Class ////////////////////////////////////////////////////////////
|
|
198
|
+
class Random {
|
|
199
|
+
constructor(seed) {
|
|
200
|
+
this.seed = seed || Math.random().toString(16).substr(2, 32).padEnd(32, '0') +
|
|
201
|
+
Math.random().toString(16).substr(2, 32).padEnd(32, '0');
|
|
202
|
+
this.useA = false;
|
|
203
|
+
let sfc32 = function(uint128Hex) {
|
|
204
|
+
let a = parseInt(uint128Hex.substr(0, 8), 16);
|
|
205
|
+
let b = parseInt(uint128Hex.substr(8, 8), 16);
|
|
206
|
+
let c = parseInt(uint128Hex.substr(16, 8), 16);
|
|
207
|
+
let d = parseInt(uint128Hex.substr(24, 8), 16);
|
|
208
|
+
return function() {
|
|
209
|
+
a |= 0; b |= 0; c |= 0; d |= 0;
|
|
210
|
+
let t = (((a + b) | 0) + d) | 0;
|
|
211
|
+
d = (d + 1) | 0;
|
|
212
|
+
a = b ^ (b >>> 9);
|
|
213
|
+
b = (c + (c << 3)) | 0;
|
|
214
|
+
c = (c << 21) | (c >>> 11);
|
|
215
|
+
c = (c + t) | 0;
|
|
216
|
+
return (t >>> 0) / 4294967296;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
this.prngA = new sfc32(this.seed.substr(0, 32));
|
|
220
|
+
this.prngB = new sfc32(this.seed.substr(32, 32));
|
|
221
|
+
for (let i = 0; i < 1e6; i += 2) {
|
|
222
|
+
this.prngA();
|
|
223
|
+
this.prngB();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
random_dec() {
|
|
228
|
+
this.useA = !this.useA;
|
|
229
|
+
return this.useA ? this.prngA() : this.prngB();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
random_num(a, b) {
|
|
233
|
+
return a + (b - a) * this.random_dec();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
random_int(a, b) {
|
|
237
|
+
return Math.floor(this.random_num(a, b + 1));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
random_bool(p) {
|
|
241
|
+
return this.random_dec() < p;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
random_choice(list) {
|
|
245
|
+
return list[this.random_int(0, list.length - 1)];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Global random instance
|
|
250
|
+
var R = new Random();`
|
|
251
|
+
}
|