Add yuruyurau_5 (#3) equation and merge feature
Browse files- Add 5th equation preset (#3): yuruyurau_5 with nested double-sin,
alternating m parameter, and 7 mutable trig sites
- Add merge zone UI below base equation selector: drag any of the 5
equation chips to layer a second equation onto all variant canvases
- Merged equations both rendered per frame; evolution mutates combined
parameter set (_2_ prefix for secondary equation params)
- Refactor rendering into shared drawEquationPoints() helper,
eliminating ~400 lines of duplicated if-else render chains
- Backend: merged_secondary_set field, set_merged_secondary API endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- genetic_backend.py +78 -6
- templates/genetic_evolver.html +332 -453
genetic_backend.py
CHANGED
|
@@ -32,7 +32,7 @@ class EquationGene:
|
|
| 32 |
"""
|
| 33 |
keys = trig_keys if trig_keys is not None else [k for k in self.params.keys() if k.startswith('trig')]
|
| 34 |
# Get all constant keys (numeric parameters, not trig function names)
|
| 35 |
-
constant_keys = [k for k in self.params.keys() if not k.startswith('trig') and isinstance(self.params.get(k), (int, float))]
|
| 36 |
|
| 37 |
self.mutation_log = []
|
| 38 |
|
|
@@ -119,6 +119,7 @@ class GeneticEquationEvolver:
|
|
| 119 |
self.systematic_phase_generations = 3 # First 3 generations are systematic
|
| 120 |
self.approved_trig_sites: set = set() # Track which trig sites user has approved (selected)
|
| 121 |
self.current_base_set = 'always_finding_yourself' # Current base equation set name
|
|
|
|
| 122 |
self.jump_size_multiplier = 1.0 # Multiplier for mutation delta (controls jump size)
|
| 123 |
self.trig_mutation_prob_base = 1.0 # Base value for auto-decay (starts at 1.0, can be set by user)
|
| 124 |
self.trig_mutation_prob_base_generation = 0 # Generation when base was last set (for decay calculation)
|
|
@@ -216,6 +217,37 @@ class GeneticEquationEvolver:
|
|
| 216 |
'trig_q_sin2': 'sin', # sin(k*d/3) in q
|
| 217 |
'trig_px_sin': 'sin', # sin(c) in px
|
| 218 |
'trig_py_cos': 'cos' # cos(c-i%2+i%5*3+7) in py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
}
|
| 220 |
}
|
| 221 |
|
|
@@ -223,13 +255,20 @@ class GeneticEquationEvolver:
|
|
| 223 |
|
| 224 |
@property
|
| 225 |
def base_params(self):
|
| 226 |
-
"""Get current base params based on selected equation set"""
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
@property
|
| 230 |
def trig_keys(self):
|
| 231 |
-
"""Get trig keys for current base equation set"""
|
| 232 |
-
return [k for k in self.base_params.keys() if k.startswith('trig')]
|
| 233 |
|
| 234 |
def get_trig_mutation_probability(self) -> float:
|
| 235 |
"""Calculate probability of trig mutations vs constant mutations.
|
|
@@ -744,6 +783,7 @@ class GeneticEquationEvolver:
|
|
| 744 |
'trig_mutation_prob_base_generation': self.trig_mutation_prob_base_generation,
|
| 745 |
'jump_size_multiplier': self.jump_size_multiplier,
|
| 746 |
}
|
|
|
|
| 747 |
if not lite:
|
| 748 |
data['base_params'] = self.base_params
|
| 749 |
data['parameter_preferences'] = self.parameter_preferences
|
|
@@ -753,6 +793,7 @@ class GeneticEquationEvolver:
|
|
| 753 |
"""Change the base equation set and reset population"""
|
| 754 |
if set_name in self.base_equation_sets:
|
| 755 |
self.current_base_set = set_name
|
|
|
|
| 756 |
# Reset all state when changing equation sets
|
| 757 |
self.generation = 0
|
| 758 |
self.trig_mutation_prob_base = 1.0
|
|
@@ -767,6 +808,23 @@ class GeneticEquationEvolver:
|
|
| 767 |
return True
|
| 768 |
return False
|
| 769 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
# Global evolver instance
|
| 771 |
evolver = GeneticEquationEvolver()
|
| 772 |
|
|
@@ -852,6 +910,8 @@ def reset_all():
|
|
| 852 |
evolver.trig_mutation_prob_base_generation = 0 # Reset generation offset
|
| 853 |
evolver.jump_size_multiplier = 1.0 # Reset jump size to default
|
| 854 |
|
|
|
|
|
|
|
| 855 |
# Reinitialize population from scratch
|
| 856 |
evolver.initialize_population()
|
| 857 |
|
|
@@ -880,5 +940,17 @@ def set_trig_mutation_prob():
|
|
| 880 |
except Exception as e:
|
| 881 |
return jsonify({'error': str(e)}), 500
|
| 882 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
if __name__ == '__main__':
|
| 884 |
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
|
|
| 32 |
"""
|
| 33 |
keys = trig_keys if trig_keys is not None else [k for k in self.params.keys() if k.startswith('trig')]
|
| 34 |
# Get all constant keys (numeric parameters, not trig function names)
|
| 35 |
+
constant_keys = [k for k in self.params.keys() if not k.startswith('trig') and not k.startswith('_2_trig') and isinstance(self.params.get(k), (int, float))]
|
| 36 |
|
| 37 |
self.mutation_log = []
|
| 38 |
|
|
|
|
| 119 |
self.systematic_phase_generations = 3 # First 3 generations are systematic
|
| 120 |
self.approved_trig_sites: set = set() # Track which trig sites user has approved (selected)
|
| 121 |
self.current_base_set = 'always_finding_yourself' # Current base equation set name
|
| 122 |
+
self.merged_secondary_set = None # Optional secondary equation for merge mode
|
| 123 |
self.jump_size_multiplier = 1.0 # Multiplier for mutation delta (controls jump size)
|
| 124 |
self.trig_mutation_prob_base = 1.0 # Base value for auto-decay (starts at 1.0, can be set by user)
|
| 125 |
self.trig_mutation_prob_base_generation = 0 # Generation when base was last set (for decay calculation)
|
|
|
|
| 217 |
'trig_q_sin2': 'sin', # sin(k*d/3) in q
|
| 218 |
'trig_px_sin': 'sin', # sin(c) in px
|
| 219 |
'trig_py_cos': 'cos' # cos(c-i%2+i%5*3+7) in py
|
| 220 |
+
},
|
| 221 |
+
'yuruyurau_5': {
|
| 222 |
+
# Equation: a=(m,d=mag(k=9*cos(i/61),e=i/692-13)**2/99+1)=>point(
|
| 223 |
+
# (q=79-e/2*sin(k/d*4)+k/d*(8+5*sin(sin(d*d+e/9-t+m))))*cos(c=d/2+cos(t-d*2+m)/9-t/16+m)+200,
|
| 224 |
+
# (q+40)*sin(c)+200)
|
| 225 |
+
# t=0,draw=$=>{t||createCanvas(w=400,w);background(9).stroke(w,96);for(t+=PI/45,i=2e4;i--;)a(i%2*9)}
|
| 226 |
+
# k = k_mult * cos(i / k_cos_div)
|
| 227 |
+
'k_mult': 9, 'k_cos_div': 61,
|
| 228 |
+
# e = i / e_div - e_sub
|
| 229 |
+
'e_div': 692, 'e_sub': 13,
|
| 230 |
+
# d = mag(k,e)**d_pow / d_div + d_add
|
| 231 |
+
'd_pow': 2, 'd_div': 99, 'd_add': 1,
|
| 232 |
+
# m = (i%2) * m_mult (alternates 0 or m_mult per point)
|
| 233 |
+
'm_mult': 9,
|
| 234 |
+
# q = q_base - e/q_e_div*sin1(k/d*q_sin1_mult) + k/d*(q_k_mult + q_sin2_mult*sin2(sin3(d*d+e/q_sin_e_div-t+m)))
|
| 235 |
+
'q_base': 79, 'q_e_div': 2, 'q_sin1_mult': 4,
|
| 236 |
+
'q_k_mult': 8, 'q_sin2_mult': 5, 'q_sin_e_div': 9,
|
| 237 |
+
# c = d/c_d_div + cosc(t - d*c_d2_mult + m)/c_cos_div - t/c_t_div + m
|
| 238 |
+
'c_d_div': 2, 'c_d2_mult': 2, 'c_cos_div': 9, 'c_t_div': 16,
|
| 239 |
+
# px = q * cosp(c) + px_add
|
| 240 |
+
'px_add': 200,
|
| 241 |
+
# py = (q + py_q_add) * sinp(c) + py_add
|
| 242 |
+
'py_q_add': 40, 'py_add': 200,
|
| 243 |
+
# Trig sites:
|
| 244 |
+
'trig_k_cos': 'cos', # cos(i/k_cos_div) in k
|
| 245 |
+
'trig_q_sin1': 'sin', # outer sin in e term: -e/q_e_div*sin(k/d*q_sin1_mult)
|
| 246 |
+
'trig_q_sin2': 'sin', # outer sin in nested double-sin: q_sin2_mult*sin(sin(...))
|
| 247 |
+
'trig_q_sin3': 'sin', # inner sin in nested double-sin: sin(d*d+e/q_sin_e_div-t+m)
|
| 248 |
+
'trig_c_cos': 'cos', # cos(t-d*c_d2_mult+m) in c
|
| 249 |
+
'trig_px_cos': 'cos', # cos(c) in px
|
| 250 |
+
'trig_py_sin': 'sin', # sin(c) in py
|
| 251 |
}
|
| 252 |
}
|
| 253 |
|
|
|
|
| 255 |
|
| 256 |
@property
|
| 257 |
def base_params(self):
|
| 258 |
+
"""Get current base params based on selected equation set (and merged secondary if active)"""
|
| 259 |
+
primary = self.base_equation_sets.get(self.current_base_set, self.base_equation_sets['always_finding_yourself'])
|
| 260 |
+
if self.merged_secondary_set and self.merged_secondary_set in self.base_equation_sets:
|
| 261 |
+
secondary = self.base_equation_sets[self.merged_secondary_set]
|
| 262 |
+
combined = dict(primary)
|
| 263 |
+
for k, v in secondary.items():
|
| 264 |
+
combined['_2_' + k] = v
|
| 265 |
+
return combined
|
| 266 |
+
return primary
|
| 267 |
+
|
| 268 |
@property
|
| 269 |
def trig_keys(self):
|
| 270 |
+
"""Get trig keys for current base equation set (including merged secondary)"""
|
| 271 |
+
return [k for k in self.base_params.keys() if k.startswith('trig') or k.startswith('_2_trig')]
|
| 272 |
|
| 273 |
def get_trig_mutation_probability(self) -> float:
|
| 274 |
"""Calculate probability of trig mutations vs constant mutations.
|
|
|
|
| 783 |
'trig_mutation_prob_base_generation': self.trig_mutation_prob_base_generation,
|
| 784 |
'jump_size_multiplier': self.jump_size_multiplier,
|
| 785 |
}
|
| 786 |
+
data['merged_secondary_set'] = self.merged_secondary_set
|
| 787 |
if not lite:
|
| 788 |
data['base_params'] = self.base_params
|
| 789 |
data['parameter_preferences'] = self.parameter_preferences
|
|
|
|
| 793 |
"""Change the base equation set and reset population"""
|
| 794 |
if set_name in self.base_equation_sets:
|
| 795 |
self.current_base_set = set_name
|
| 796 |
+
self.merged_secondary_set = None # Clear merge when changing base set
|
| 797 |
# Reset all state when changing equation sets
|
| 798 |
self.generation = 0
|
| 799 |
self.trig_mutation_prob_base = 1.0
|
|
|
|
| 808 |
return True
|
| 809 |
return False
|
| 810 |
|
| 811 |
+
def set_merged_secondary(self, set_name: Optional[str]):
|
| 812 |
+
"""Set (or clear) the secondary equation for merge mode and reinitialize population"""
|
| 813 |
+
if set_name is None or set_name in self.base_equation_sets:
|
| 814 |
+
self.merged_secondary_set = set_name
|
| 815 |
+
# Reset evolution state for the new combined genome
|
| 816 |
+
self.generation = 0
|
| 817 |
+
self.trig_mutation_prob_base = 1.0
|
| 818 |
+
self.trig_mutation_prob_base_generation = 0
|
| 819 |
+
self.approved_trig_sites = set()
|
| 820 |
+
self.parameter_preferences = {}
|
| 821 |
+
self.mutation_penalties = {}
|
| 822 |
+
self.liked_variants = []
|
| 823 |
+
self.select_none_history = []
|
| 824 |
+
self.initialize_population()
|
| 825 |
+
return True
|
| 826 |
+
return False
|
| 827 |
+
|
| 828 |
# Global evolver instance
|
| 829 |
evolver = GeneticEquationEvolver()
|
| 830 |
|
|
|
|
| 910 |
evolver.trig_mutation_prob_base_generation = 0 # Reset generation offset
|
| 911 |
evolver.jump_size_multiplier = 1.0 # Reset jump size to default
|
| 912 |
|
| 913 |
+
evolver.merged_secondary_set = None # Clear merge on full reset
|
| 914 |
+
|
| 915 |
# Reinitialize population from scratch
|
| 916 |
evolver.initialize_population()
|
| 917 |
|
|
|
|
| 940 |
except Exception as e:
|
| 941 |
return jsonify({'error': str(e)}), 500
|
| 942 |
|
| 943 |
+
@app.route('/api/set_merged_secondary', methods=['POST'])
|
| 944 |
+
def set_merged_secondary():
|
| 945 |
+
"""Set or clear the secondary equation for merge mode."""
|
| 946 |
+
try:
|
| 947 |
+
data = request.get_json()
|
| 948 |
+
set_name = data.get('set_name') # None/null to clear merge
|
| 949 |
+
if evolver.set_merged_secondary(set_name):
|
| 950 |
+
return jsonify({'success': True, 'merged_secondary_set': evolver.merged_secondary_set})
|
| 951 |
+
return jsonify({'success': False, 'error': 'Invalid equation set name'}), 400
|
| 952 |
+
except Exception as e:
|
| 953 |
+
return jsonify({'error': str(e)}), 500
|
| 954 |
+
|
| 955 |
if __name__ == '__main__':
|
| 956 |
app.run(debug=True, host='0.0.0.0', port=5000)
|
templates/genetic_evolver.html
CHANGED
|
@@ -44,6 +44,14 @@
|
|
| 44 |
.btn{background:#000;color:#fff;border:1px solid #666;padding:8px 16px;margin:0;cursor:pointer;font-family:monospace;font-size:11px;text-transform:uppercase;letter-spacing:0.5px}
|
| 45 |
.btn:hover{background:#333;border-color:#999}
|
| 46 |
.btn:disabled{background:#111;color:#666;border-color:#444;cursor:not-allowed}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</style>
|
| 48 |
</head>
|
| 49 |
<body>
|
|
@@ -70,9 +78,24 @@
|
|
| 70 |
<!-- <option value="yuruyurau_2">yuruyurau #2</option> -->
|
| 71 |
<!-- <option value="yuruyurau_3">yuruyurau #3</option> -->
|
| 72 |
<option value="yuruyurau_4">#2</option>
|
|
|
|
| 73 |
</select>
|
| 74 |
</div>
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
<div style="margin-bottom:15px;">
|
| 77 |
<label style="font-size:9px;color:#666;text-transform:lowercase;letter-spacing:1px;display:block;margin-bottom:5px;">trigonometric mutation probability:</label>
|
| 78 |
<div style="display:flex;gap:4px;align-items:center;height:20px;">
|
|
@@ -191,6 +214,7 @@
|
|
| 191 |
|
| 192 |
<script>
|
| 193 |
let variants=[]; let selectedIds=new Set();
|
|
|
|
| 194 |
let currentParams = {
|
| 195 |
k_div: 4, k_sub: 10.5, e_div: 9, e_add: 9, o_div: 9, x_offset: 60,
|
| 196 |
y_trig_div: 8, q_mult: 0.7, px_add: 200, py_add: 200, py_q_div: 3,
|
|
@@ -200,6 +224,7 @@
|
|
| 200 |
let currentBaseSet = 'always_finding_yourself'; // Current base equation set
|
| 201 |
let variantSketches = []; // Track p5 instances for variants to clean them up
|
| 202 |
let sketchBaseSet = null; // Which base set the current sketches were built for
|
|
|
|
| 203 |
// Shared mutable param objects β p5 sketches hold references so we can update without recreation
|
| 204 |
const variantParamSlots = Array.from({length: 16}, () => ({}));
|
| 205 |
let currentMutations = []; // Track mutations for highlighting
|
|
@@ -223,7 +248,8 @@
|
|
| 223 |
'yuruyurau': 6, // for(let i = 0; i < 12000; i += 6)
|
| 224 |
'yuruyurau_2': 1, // for(let i = 30000; i > 0; i--)
|
| 225 |
'yuruyurau_3': 1, // for(let i = 20000; i > 0; i--)
|
| 226 |
-
'yuruyurau_4': 1
|
|
|
|
| 227 |
};
|
| 228 |
const LOOP_BOUNDS = {
|
| 229 |
'always_finding_yourself': 6400,
|
|
@@ -231,7 +257,8 @@
|
|
| 231 |
'yuruyurau': 12000,
|
| 232 |
'yuruyurau_2': 10000,
|
| 233 |
'yuruyurau_3': 10000,
|
| 234 |
-
'yuruyurau_4': 1000
|
|
|
|
| 235 |
};
|
| 236 |
|
| 237 |
// O(1) trig dispatch β avoids per-point if-else chain
|
|
@@ -265,6 +292,10 @@
|
|
| 265 |
currentBaseSet = data.current_base_set;
|
| 266 |
document.getElementById('base-equation-selector').value = currentBaseSet;
|
| 267 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
if(data.trig_mutation_prob !== undefined) {
|
| 269 |
trigMutationProb = data.trig_mutation_prob;
|
| 270 |
updateMutationProbChart();
|
|
@@ -379,6 +410,8 @@
|
|
| 379 |
.then(data=>{
|
| 380 |
if(data.success) {
|
| 381 |
currentBaseSet = newSet;
|
|
|
|
|
|
|
| 382 |
loadPopulation();
|
| 383 |
} else {
|
| 384 |
alert('Error changing base equation set: '+data.error);
|
|
@@ -451,6 +484,18 @@
|
|
| 451 |
equation += ` px = q*${getTrigSpan('trig_px_sin')}(c) + ${getParamSpan('px_add')}<br>`;
|
| 452 |
equation += ` py = q*${getTrigSpan('trig_py_cos')}(c-i%${getParamSpan('c_mod2')}+i%${getParamSpan('c_mod1')}*3+${getParamSpan('py_cos_add1')}) + ${getParamSpan('py_add')}<br>`;
|
| 453 |
equation += ` point(px,py)`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
} else {
|
| 455 |
// Original equations (always_finding_yourself)
|
| 456 |
equation += 'for i in range(0, 6400, 3):<br>';
|
|
@@ -471,6 +516,167 @@
|
|
| 471 |
|
| 472 |
let basePreviewSketch = null;
|
| 473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
function updateBaseEquationDisplay(){
|
| 475 |
const container = document.getElementById('base-preview-canvas');
|
| 476 |
|
|
@@ -483,10 +689,8 @@
|
|
| 483 |
const baseParams = {...currentParams};
|
| 484 |
|
| 485 |
basePreviewSketch = new p5(p => {
|
| 486 |
-
const trig = (name, val) => getTrig(name)(val);
|
| 487 |
-
|
| 488 |
p.setup = () => {
|
| 489 |
-
p.createCanvas(126, 126).parent(container);
|
| 490 |
p.pixelDensity(1);
|
| 491 |
p.frameRate(FRAME_RATE);
|
| 492 |
p.noFill();
|
|
@@ -494,227 +698,13 @@
|
|
| 494 |
};
|
| 495 |
|
| 496 |
p.draw = () => {
|
| 497 |
-
// Animate over 300 frames, repeating (100x speed)
|
| 498 |
const frame = p.frameCount % ANIMATION_FRAMES;
|
| 499 |
-
const t = (frame / ANIMATION_FRAMES) * Math.PI * 2 * 100;
|
| 500 |
-
|
| 501 |
-
p.
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
const centerX = p.width / 2;
|
| 506 |
-
const centerY = p.height / 2;
|
| 507 |
-
|
| 508 |
-
if(currentBaseSet === 'a_constant_state_of_bliss') {
|
| 509 |
-
// Render "a constant state of bliss" equations
|
| 510 |
-
const zoomFactor = 0.4; // 0.8x zoom out from 0.5
|
| 511 |
-
const kDiv = baseParams.k_div || 8, kSub = baseParams.k_sub || 11.5;
|
| 512 |
-
const eDiv = baseParams.e_div || 8, eSub = baseParams.e_sub || 12.5;
|
| 513 |
-
const oDiv = baseParams.o_div || 139, dMult = baseParams.d_mult || 10;
|
| 514 |
-
const pxDiv = baseParams.px_div || 2, pxAdd1 = baseParams.px_add1 || 150;
|
| 515 |
-
const pyYDiv = baseParams.py_y_div || 9, pyDMult = baseParams.py_d_mult || 15;
|
| 516 |
-
const pyCosMult = baseParams.py_cos_mult || 2, pyAdd = baseParams.py_add || 220;
|
| 517 |
-
const strokeBase = baseParams.stroke_base || 99, strokeMult = baseParams.stroke_mult || 99, strokePow = baseParams.stroke_pow || 30;
|
| 518 |
-
// Trig functions
|
| 519 |
-
const trigDCos = baseParams.trig_d_cos || 'cos';
|
| 520 |
-
const trigPxSin1 = baseParams.trig_px_sin1 || 'sin';
|
| 521 |
-
const trigPxSin2 = baseParams.trig_px_sin2 || 'sin';
|
| 522 |
-
const trigPyCos = baseParams.trig_py_cos || 'cos';
|
| 523 |
-
const trigPySin = baseParams.trig_py_sin || 'sin';
|
| 524 |
-
const trigStrokeSin1 = baseParams.trig_stroke_sin1 || 'sin';
|
| 525 |
-
const trigStrokeSin2 = baseParams.trig_stroke_sin2 || 'sin';
|
| 526 |
-
|
| 527 |
-
// Pattern center in world coordinates (approximate, based on typical px/py values)
|
| 528 |
-
// For this equation, px typically ranges around pxAdd1 (150), py around pyAdd (220)
|
| 529 |
-
const patternCenterX = pxAdd1 * zoomFactor;
|
| 530 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 531 |
-
|
| 532 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 533 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 6400;
|
| 534 |
-
for(let i = 0; i < maxI; i += step){
|
| 535 |
-
let x = i % 200, y = i / 200;
|
| 536 |
-
let k = x / kDiv - kSub;
|
| 537 |
-
let e = y / eDiv - eSub;
|
| 538 |
-
let o = Math.hypot(k, e) ** 2 / oDiv;
|
| 539 |
-
let d = dMult * trig(trigDCos, o);
|
| 540 |
-
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 541 |
-
let stroke_r = strokeBase + strokeMult / trig(trigStrokeSin1, ksafe) * trig(trigStrokeSin2, t + e) ** strokePow;
|
| 542 |
-
p.stroke(stroke_r, 66);
|
| 543 |
-
let px = ((x + trig(trigPxSin1, d) * d * k) / pxDiv + pxAdd1 + o * k * trig(trigPxSin2, t + d * o)) * zoomFactor;
|
| 544 |
-
let py = (y / pyYDiv - d * pyDMult - trig(trigPyCos, d * pyCosMult) * d + pyAdd + d * trig(trigPySin, d - t)) * zoomFactor;
|
| 545 |
-
// Center the pattern in the canvas
|
| 546 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 547 |
-
}
|
| 548 |
-
} else if(currentBaseSet === 'yuruyurau') {
|
| 549 |
-
// Render yuruyurau equation
|
| 550 |
-
p.stroke(255, 96); // Match original stroke(w,96)
|
| 551 |
-
const zoomFactor = 0.3;
|
| 552 |
-
const kBase = baseParams.k_base || 4, kSinMult = baseParams.k_sin_mult || 3, kCosDiv = baseParams.k_cos_div || 29;
|
| 553 |
-
const eDiv = baseParams.e_div || 8, eSub = baseParams.e_sub || 13;
|
| 554 |
-
const qSin1Mult = baseParams.q_sin1_mult || 3, qSin1Mult2 = baseParams.q_sin1_mult2 || 2, qDiv = baseParams.q_div || 0.3;
|
| 555 |
-
const qSin2Div = baseParams.q_sin2_div || 25, qBase = baseParams.q_base || 9;
|
| 556 |
-
const qSin3Mult = baseParams.q_sin3_mult || 4, qSin3EMult = baseParams.q_sin3_e_mult || 9;
|
| 557 |
-
const qSin3DMult = baseParams.q_sin3_d_mult || 3, qSin3TMult = baseParams.q_sin3_t_mult || 2;
|
| 558 |
-
const pxCosMult = baseParams.px_cos_mult || 30, pxAdd = baseParams.px_add || 200;
|
| 559 |
-
const pyDMult = baseParams.py_d_mult || 39, pySub = baseParams.py_sub || 220;
|
| 560 |
-
// Trig functions
|
| 561 |
-
const trigKSin = baseParams.trig_k_sin || 'sin';
|
| 562 |
-
const trigKCos = baseParams.trig_k_cos || 'cos';
|
| 563 |
-
const trigQSin1 = baseParams.trig_q_sin1 || 'sin';
|
| 564 |
-
const trigQSin2 = baseParams.trig_q_sin2 || 'sin';
|
| 565 |
-
const trigQSin3 = baseParams.trig_q_sin3 || 'sin';
|
| 566 |
-
const trigPxCos = baseParams.trig_px_cos || 'cos';
|
| 567 |
-
const trigPySin = baseParams.trig_py_sin || 'sin';
|
| 568 |
-
|
| 569 |
-
// Pattern center estimate
|
| 570 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 571 |
-
const patternCenterY = (pySub / 2) * zoomFactor;
|
| 572 |
-
|
| 573 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 574 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 10000;
|
| 575 |
-
for(let i = 0; i < maxI; i += step){
|
| 576 |
-
let x = i, y = i / 235;
|
| 577 |
-
let k = (kBase + trig(trigKSin, y * 2 - t) * kSinMult) * trig(trigKCos, x / kCosDiv);
|
| 578 |
-
let e = y / eDiv - eSub;
|
| 579 |
-
let d = Math.hypot(k, e);
|
| 580 |
-
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 581 |
-
let q = qSin1Mult * trig(trigQSin1, k * qSin1Mult2) + qDiv / ksafe + trig(trigQSin2, y / qSin2Div) * k * (qBase + qSin3Mult * trig(trigQSin3, e * qSin3EMult - d * qSin3DMult + t * qSin3TMult));
|
| 582 |
-
let c = d - t;
|
| 583 |
-
let px = (q + pxCosMult * trig(trigPxCos, c) + pxAdd) * zoomFactor;
|
| 584 |
-
let py = (q * trig(trigPySin, c) + d * pyDMult - pySub) * zoomFactor;
|
| 585 |
-
// Center the pattern in the canvas
|
| 586 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 587 |
-
}
|
| 588 |
-
/* } else if(currentBaseSet === 'yuruyurau_2') {
|
| 589 |
-
// Render yuruyurau #2
|
| 590 |
-
p.stroke(255, 46);
|
| 591 |
-
const zoomFactor = 0.3;
|
| 592 |
-
const kCondThreshold = baseParams.k_cond_threshold || 20000;
|
| 593 |
-
const kSinDiv = baseParams.k_sin_div || 9, kSinMult = baseParams.k_sin_mult || 9;
|
| 594 |
-
const kCosMult = baseParams.k_cos_mult || 4, kCosDiv1 = baseParams.k_cos_div1 || 49, kCosDiv2 = baseParams.k_cos_div2 || 3690;
|
| 595 |
-
const eDiv = baseParams.e_div || 984, eSub = baseParams.e_sub || 12;
|
| 596 |
-
const dPow = baseParams.d_pow || 2, dDiv = baseParams.d_div || 99, dAdd = baseParams.d_add || 1;
|
| 597 |
-
const qKMult = baseParams.q_k_mult || 4, qSinDMult = baseParams.q_sin_d_mult || 16;
|
| 598 |
-
const qAtan2Mult = baseParams.q_atan2_mult || 5, qAtan2Mult2 = baseParams.q_atan2_mult2 || 9;
|
| 599 |
-
const pxSinMult = baseParams.px_sin_mult || 60, pxAdd = baseParams.px_add || 200;
|
| 600 |
-
const pyQAdd = baseParams.py_q_add || 40, pyDMult = baseParams.py_d_mult || 79;
|
| 601 |
-
const cDMult = baseParams.c_d_mult || 1.1, cTDiv = baseParams.c_t_div || 18;
|
| 602 |
-
const trigKSin = baseParams.trig_k_sin || 'sin';
|
| 603 |
-
const trigKCos1 = baseParams.trig_k_cos1 || 'cos';
|
| 604 |
-
const trigKCos2 = baseParams.trig_k_cos2 || 'cos';
|
| 605 |
-
const trigQSin = baseParams.trig_q_sin || 'sin';
|
| 606 |
-
const trigQAtan2 = baseParams.trig_q_atan2 || 'atan';
|
| 607 |
-
const trigPxSin = baseParams.trig_px_sin || 'sin';
|
| 608 |
-
const trigPySin = baseParams.trig_py_sin || 'sin';
|
| 609 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 610 |
-
const patternCenterY = (pyDMult * 50) * zoomFactor;
|
| 611 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 612 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 30000;
|
| 613 |
-
for(let i = maxI; i > 0; i -= step){
|
| 614 |
-
let k = i < kCondThreshold ? trig(trigKSin, i / kSinDiv) * kSinMult : kCosMult * trig(trigKCos1, i / kCosDiv1) * trig(trigKCos2, i / kCosDiv2);
|
| 615 |
-
let e = i / eDiv - eSub;
|
| 616 |
-
let d = (Math.hypot(k, e) ** dPow / dDiv) + dAdd;
|
| 617 |
-
let q = k * (qKMult + trig(trigQSin, d * qSinDMult - t + k)) - qAtan2Mult * Math.atan2(k, e) * qAtan2Mult2;
|
| 618 |
-
let c = d * cDMult - t / cTDiv + (i % 2) * 3;
|
| 619 |
-
let px = (q + pxSinMult * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 620 |
-
let py = ((q + pyQAdd) * trig(trigPySin, c - d) + d * pyDMult) * zoomFactor;
|
| 621 |
-
if(isFinite(px) && isFinite(py)) {
|
| 622 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 623 |
-
}
|
| 624 |
-
}
|
| 625 |
-
} else if(currentBaseSet === 'yuruyurau_3') {
|
| 626 |
-
// Render yuruyurau #3
|
| 627 |
-
const zoomFactor = 0.3;
|
| 628 |
-
const kMult = baseParams.k_mult || 5, kCosDiv1 = baseParams.k_cos_div1 || 49, kCosDiv2 = baseParams.k_cos_div2 || 3690;
|
| 629 |
-
const eDiv = baseParams.e_div || 984, eSub = baseParams.e_sub || 12;
|
| 630 |
-
const dPow = baseParams.d_pow || 2, dDiv = baseParams.d_div || 99, dAdd = baseParams.d_add || 1;
|
| 631 |
-
const qKMult = baseParams.q_k_mult || 4, qSinDMult = baseParams.q_sin_d_mult || 18, qSinTMult = baseParams.q_sin_t_mult || 2;
|
| 632 |
-
const qSinMod = baseParams.q_sin_mod || 3, qSinModMult = baseParams.q_sin_mod_mult || 2;
|
| 633 |
-
const qAtan2Mult = baseParams.q_atan2_mult || 5, qAtan2Mult2 = baseParams.q_atan2_mult2 || 9;
|
| 634 |
-
const pxSinMult = baseParams.px_sin_mult || 30, pxAdd = baseParams.px_add || 200;
|
| 635 |
-
const pySinMult = baseParams.py_sin_mult || 80, pyDMult = baseParams.py_d_mult || 79;
|
| 636 |
-
const cTDiv = baseParams.c_t_div || 18, cMod = baseParams.c_mod || 3, cModMult = baseParams.c_mod_mult || 4;
|
| 637 |
-
const trigKCos1 = baseParams.trig_k_cos1 || 'cos';
|
| 638 |
-
const trigKCos2 = baseParams.trig_k_cos2 || 'cos';
|
| 639 |
-
const trigQSin = baseParams.trig_q_sin || 'sin';
|
| 640 |
-
const trigQAtan2 = baseParams.trig_q_atan2 || 'atan';
|
| 641 |
-
const trigPxSin = baseParams.trig_px_sin || 'sin';
|
| 642 |
-
const trigPySin = baseParams.trig_py_sin || 'sin';
|
| 643 |
-
const trigStrokeCos = baseParams.trig_stroke_cos || 'cos';
|
| 644 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 645 |
-
const patternCenterY = (pyDMult * 50) * zoomFactor;
|
| 646 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 647 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 20000;
|
| 648 |
-
for(let i = maxI; i > 0; i -= step){
|
| 649 |
-
let k = kMult * trig(trigKCos1, i / kCosDiv1) * trig(trigKCos2, i / kCosDiv2);
|
| 650 |
-
let e = i / eDiv - eSub;
|
| 651 |
-
let d = (Math.hypot(k, e) ** dPow / dDiv) + dAdd;
|
| 652 |
-
let cosVal = trig(trigStrokeCos, t + e);
|
| 653 |
-
let stroke_r = 36 + 3 / (Math.abs(cosVal) > 0.001 ? cosVal : 0.001);
|
| 654 |
-
p.stroke(255, Math.min(255, Math.max(0, stroke_r)));
|
| 655 |
-
let c = d - t / cTDiv + (i % cMod) * cModMult;
|
| 656 |
-
let px = (k * (qKMult + trig(trigQSin, d * qSinDMult - t * qSinTMult + (i % qSinMod) * qSinModMult)) - qAtan2Mult * Math.atan2(k, e) * qAtan2Mult2 + pxSinMult * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 657 |
-
let py = (pySinMult * trig(trigPySin, c - d) + d * pyDMult) * zoomFactor;
|
| 658 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 659 |
-
}
|
| 660 |
-
*/ } else if(currentBaseSet === 'yuruyurau_4') {
|
| 661 |
-
// Render yuruyurau #4
|
| 662 |
-
p.stroke(255, 96);
|
| 663 |
-
const zoomFactor = 0.3;
|
| 664 |
-
const kMult = baseParams.k_mult || 5, kCosDiv = baseParams.k_cos_div || 8;
|
| 665 |
-
const eMult = baseParams.e_mult || 5, eCosDiv = baseParams.e_cos_div || 9;
|
| 666 |
-
const dDivBase = baseParams.d_div_base || 6, dPow = baseParams.d_pow || 4, dAdd = baseParams.d_add || 4;
|
| 667 |
-
const qKMult = baseParams.q_k_mult || 3, qEDiv = baseParams.q_e_div || 2;
|
| 668 |
-
const qSinMult = baseParams.q_sin_mult || 3, qSinKDDiv = baseParams.q_sin_kd_div || 3, qBitwiseMult = baseParams.q_bitwise_mult || 70;
|
| 669 |
-
const pxSinMult = baseParams.px_sin_mult || 1, pxAdd = baseParams.px_add || 200;
|
| 670 |
-
const pyCosAdd1 = baseParams.py_cos_add1 || 7, pyAdd = baseParams.py_add || 200;
|
| 671 |
-
const cTDiv = baseParams.c_t_div || 9, cMod1 = baseParams.c_mod1 || 5, cMod1Mult = baseParams.c_mod1_mult || 5, cMod2 = baseParams.c_mod2 || 2;
|
| 672 |
-
const trigKCos = baseParams.trig_k_cos || 'cos';
|
| 673 |
-
const trigECos = baseParams.trig_e_cos || 'cos';
|
| 674 |
-
const trigQSin = baseParams.trig_q_sin || 'sin';
|
| 675 |
-
const trigQSin2 = baseParams.trig_q_sin2 || 'sin';
|
| 676 |
-
const trigPxSin = baseParams.trig_px_sin || 'sin';
|
| 677 |
-
const trigPyCos = baseParams.trig_py_cos || 'cos';
|
| 678 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 679 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 680 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 681 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 20000;
|
| 682 |
-
for(let i = maxI; i > 0; i -= step){
|
| 683 |
-
let y = i / 799;
|
| 684 |
-
let k = kMult * trig(trigKCos, i / kCosDiv);
|
| 685 |
-
let e = eMult * trig(trigECos, y / eCosDiv);
|
| 686 |
-
let d = (Math.hypot(k, e) / (dDivBase + (i % 5))) ** dPow + dAdd;
|
| 687 |
-
let q = k * (qKMult + e / qEDiv * trig(trigQSin, d * d - t)) - qSinMult * trig(trigQSin2, k * d / qSinKDDiv) - (~(i & 1)) * qBitwiseMult;
|
| 688 |
-
let c = d - t / cTDiv + (i % cMod1) * cMod1Mult + (i % cMod2);
|
| 689 |
-
let px = (q * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 690 |
-
let py = (q * trig(trigPyCos, c - (i % cMod2) + (i % cMod1) * 3 + pyCosAdd1) + pyAdd) * zoomFactor;
|
| 691 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 692 |
-
}
|
| 693 |
-
} else {
|
| 694 |
-
// Render "always_finding_yourself" equations
|
| 695 |
-
const zoomFactor = 0.2;
|
| 696 |
-
const kDiv = baseParams.k_div, kSub = baseParams.k_sub, eDiv = baseParams.e_div, eAdd = baseParams.e_add;
|
| 697 |
-
const oDiv = baseParams.o_div, xOff = baseParams.x_offset, yTrigDiv = baseParams.y_trig_div;
|
| 698 |
-
const qMult = baseParams.q_mult, pxAdd = baseParams.px_add, pyAdd = baseParams.py_add, pyQDiv = baseParams.py_q_div;
|
| 699 |
-
const trigY = baseParams.trig_y, trigE = baseParams.trig_e, trigO = baseParams.trig_o;
|
| 700 |
-
const trigCSin = baseParams.trig_c_sin, trigC4Cos = baseParams.trig_c4_cos, trigCCos = baseParams.trig_c_cos;
|
| 701 |
-
|
| 702 |
-
// Pattern center in world coordinates (pxAdd and pyAdd are the base offsets)
|
| 703 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 704 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 705 |
-
|
| 706 |
-
for(let i = 0; i < 6400; i += 3){
|
| 707 |
-
let x = i % 200, y = i / 200;
|
| 708 |
-
let k = x / kDiv - kSub; let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 709 |
-
let e = y / eDiv + eAdd;
|
| 710 |
-
let o = Math.hypot(k, e) / oDiv;
|
| 711 |
-
let q = x + xOff + y + 1/ksafe + o * k * (trig(trigY, y) / yTrigDiv + trig(trigE, e)) * trig(trigO, o * 4 - t);
|
| 712 |
-
let c = o + e / 99 - t / 8 + (i % 2) * 3;
|
| 713 |
-
let px = (qMult * q * trig(trigCSin, c) + pxAdd) * zoomFactor;
|
| 714 |
-
let py = (pyAdd + y / 2 * trig(trigC4Cos, c * 4 - o) - q / pyQDiv * trig(trigCCos, c - 1)) * zoomFactor;
|
| 715 |
-
// Center the pattern in the canvas
|
| 716 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 717 |
-
}
|
| 718 |
}
|
| 719 |
};
|
| 720 |
});
|
|
@@ -764,8 +754,8 @@
|
|
| 764 |
currentMutations = [];
|
| 765 |
variants.forEach(v => { if(v.mutations) currentMutations.push(...v.mutations); });
|
| 766 |
|
| 767 |
-
// Fast path: same base set + same count β just update params & labels, no sketch recreation
|
| 768 |
-
if(sketchBaseSet === currentBaseSet && variantSketches.length === variants.length) {
|
| 769 |
variants.forEach((v, i) => {
|
| 770 |
// Update shared param slot β the p5 draw loop reads from this reference each frame
|
| 771 |
Object.assign(variantParamSlots[i], v.params);
|
|
@@ -782,10 +772,11 @@
|
|
| 782 |
return;
|
| 783 |
}
|
| 784 |
|
| 785 |
-
// Full rebuild: base set changed or first render
|
| 786 |
variantSketches.forEach(s => { if(s && typeof s.remove === 'function') s.remove(); });
|
| 787 |
variantSketches = [];
|
| 788 |
sketchBaseSet = currentBaseSet;
|
|
|
|
| 789 |
grid.innerHTML = '';
|
| 790 |
|
| 791 |
variants.forEach((v, i) => {
|
|
@@ -818,9 +809,6 @@
|
|
| 818 |
|
| 819 |
function renderEquation(container, params){
|
| 820 |
const sketch = new p5(p=>{
|
| 821 |
-
// Use global TRIG_FNS lookup (O(1)) β avoids per-point if-else chain
|
| 822 |
-
const trig = (name, val) => getTrig(name)(val);
|
| 823 |
-
|
| 824 |
p.setup=()=>{
|
| 825 |
p.createCanvas(126,126).parent(container);
|
| 826 |
p.pixelDensity(1);
|
|
@@ -831,230 +819,13 @@
|
|
| 831 |
};
|
| 832 |
|
| 833 |
p.draw=()=>{
|
| 834 |
-
// Animate over 300 frames, repeating (100x speed)
|
| 835 |
const frame = p.frameCount % ANIMATION_FRAMES;
|
| 836 |
-
const t = (frame / ANIMATION_FRAMES) * Math.PI * 2 * 100;
|
| 837 |
-
|
| 838 |
p.background(0);
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
if(currentBaseSet === 'a_constant_state_of_bliss') {
|
| 845 |
-
// Render "a constant state of bliss" equations
|
| 846 |
-
const zoomFactor = 0.4; // 0.8x zoom out from 0.5
|
| 847 |
-
const kDiv = params.k_div || 8, kSub = params.k_sub || 11.5;
|
| 848 |
-
const eDiv = params.e_div || 8, eSub = params.e_sub || 12.5;
|
| 849 |
-
const oDiv = params.o_div || 139, dMult = params.d_mult || 10;
|
| 850 |
-
const pxDiv = params.px_div || 2, pxAdd1 = params.px_add1 || 150;
|
| 851 |
-
const pyYDiv = params.py_y_div || 9, pyDMult = params.py_d_mult || 15;
|
| 852 |
-
const pyCosMult = params.py_cos_mult || 2, pyAdd = params.py_add || 220;
|
| 853 |
-
const strokeBase = params.stroke_base || 99, strokeMult = params.stroke_mult || 99, strokePow = params.stroke_pow || 30;
|
| 854 |
-
// Trig functions
|
| 855 |
-
const trigDCos = params.trig_d_cos || 'cos';
|
| 856 |
-
const trigPxSin1 = params.trig_px_sin1 || 'sin';
|
| 857 |
-
const trigPxSin2 = params.trig_px_sin2 || 'sin';
|
| 858 |
-
const trigPyCos = params.trig_py_cos || 'cos';
|
| 859 |
-
const trigPySin = params.trig_py_sin || 'sin';
|
| 860 |
-
const trigStrokeSin1 = params.trig_stroke_sin1 || 'sin';
|
| 861 |
-
const trigStrokeSin2 = params.trig_stroke_sin2 || 'sin';
|
| 862 |
-
|
| 863 |
-
// Pattern center in world coordinates (approximate, based on typical px/py values)
|
| 864 |
-
// For this equation, px typically ranges around pxAdd1 (150), py around pyAdd (220)
|
| 865 |
-
const patternCenterX = pxAdd1 * zoomFactor;
|
| 866 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 867 |
-
|
| 868 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 869 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 6400;
|
| 870 |
-
for(let i = 0; i < maxI; i += step){
|
| 871 |
-
let x = i % 200, y = i / 200;
|
| 872 |
-
let k = x / kDiv - kSub;
|
| 873 |
-
let e = y / eDiv - eSub;
|
| 874 |
-
let o = Math.hypot(k, e) ** 2 / oDiv;
|
| 875 |
-
let d = dMult * trig(trigDCos, o);
|
| 876 |
-
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 877 |
-
let stroke_r = strokeBase + strokeMult / trig(trigStrokeSin1, ksafe) * trig(trigStrokeSin2, t + e) ** strokePow;
|
| 878 |
-
p.stroke(stroke_r, 66);
|
| 879 |
-
let px = ((x + trig(trigPxSin1, d) * d * k) / pxDiv + pxAdd1 + o * k * trig(trigPxSin2, t + d * o)) * zoomFactor;
|
| 880 |
-
let py = (y / pyYDiv - d * pyDMult - trig(trigPyCos, d * pyCosMult) * d + pyAdd + d * trig(trigPySin, d - t)) * zoomFactor;
|
| 881 |
-
// Center the pattern in the canvas
|
| 882 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 883 |
-
}
|
| 884 |
-
} else if(currentBaseSet === 'yuruyurau') {
|
| 885 |
-
// Render yuruyurau equation
|
| 886 |
-
p.stroke(255, 96); // Match original stroke(w,96)
|
| 887 |
-
const zoomFactor = 0.3;
|
| 888 |
-
const kBase = params.k_base || 4, kSinMult = params.k_sin_mult || 3, kCosDiv = params.k_cos_div || 29;
|
| 889 |
-
const eDiv = params.e_div || 8, eSub = params.e_sub || 13;
|
| 890 |
-
const qSin1Mult = params.q_sin1_mult || 3, qSin1Mult2 = params.q_sin1_mult2 || 2, qDiv = params.q_div || 0.3;
|
| 891 |
-
const qSin2Div = params.q_sin2_div || 25, qBase = params.q_base || 9;
|
| 892 |
-
const qSin3Mult = params.q_sin3_mult || 4, qSin3EMult = params.q_sin3_e_mult || 9;
|
| 893 |
-
const qSin3DMult = params.q_sin3_d_mult || 3, qSin3TMult = params.q_sin3_t_mult || 2;
|
| 894 |
-
const pxCosMult = params.px_cos_mult || 30, pxAdd = params.px_add || 200;
|
| 895 |
-
const pyDMult = params.py_d_mult || 39, pySub = params.py_sub || 220;
|
| 896 |
-
// Trig functions
|
| 897 |
-
const trigKSin = params.trig_k_sin || 'sin';
|
| 898 |
-
const trigKCos = params.trig_k_cos || 'cos';
|
| 899 |
-
const trigQSin1 = params.trig_q_sin1 || 'sin';
|
| 900 |
-
const trigQSin2 = params.trig_q_sin2 || 'sin';
|
| 901 |
-
const trigQSin3 = params.trig_q_sin3 || 'sin';
|
| 902 |
-
const trigPxCos = params.trig_px_cos || 'cos';
|
| 903 |
-
const trigPySin = params.trig_py_sin || 'sin';
|
| 904 |
-
|
| 905 |
-
// Pattern center estimate
|
| 906 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 907 |
-
const patternCenterY = (pySub / 2) * zoomFactor;
|
| 908 |
-
|
| 909 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 910 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 10000;
|
| 911 |
-
for(let i = 0; i < maxI; i += step){
|
| 912 |
-
let x = i, y = i / 235;
|
| 913 |
-
let k = (kBase + trig(trigKSin, y * 2 - t) * kSinMult) * trig(trigKCos, x / kCosDiv);
|
| 914 |
-
let e = y / eDiv - eSub;
|
| 915 |
-
let d = Math.hypot(k, e);
|
| 916 |
-
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 917 |
-
let q = qSin1Mult * trig(trigQSin1, k * qSin1Mult2) + qDiv / ksafe + trig(trigQSin2, y / qSin2Div) * k * (qBase + qSin3Mult * trig(trigQSin3, e * qSin3EMult - d * qSin3DMult + t * qSin3TMult));
|
| 918 |
-
let c = d - t;
|
| 919 |
-
let px = (q + pxCosMult * trig(trigPxCos, c) + pxAdd) * zoomFactor;
|
| 920 |
-
let py = (q * trig(trigPySin, c) + d * pyDMult - pySub) * zoomFactor;
|
| 921 |
-
// Center the pattern in the canvas
|
| 922 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 923 |
-
}
|
| 924 |
-
/* } else if(currentBaseSet === 'yuruyurau_2') {
|
| 925 |
-
// Render yuruyurau #2
|
| 926 |
-
p.stroke(255, 46);
|
| 927 |
-
const zoomFactor = 0.3;
|
| 928 |
-
const kCondThreshold = params.k_cond_threshold || 20000;
|
| 929 |
-
const kSinDiv = params.k_sin_div || 9, kSinMult = params.k_sin_mult || 9;
|
| 930 |
-
const kCosMult = params.k_cos_mult || 4, kCosDiv1 = params.k_cos_div1 || 49, kCosDiv2 = params.k_cos_div2 || 3690;
|
| 931 |
-
const eDiv = params.e_div || 984, eSub = params.e_sub || 12;
|
| 932 |
-
const dPow = params.d_pow || 2, dDiv = params.d_div || 99, dAdd = params.d_add || 1;
|
| 933 |
-
const qKMult = params.q_k_mult || 4, qSinDMult = params.q_sin_d_mult || 16;
|
| 934 |
-
const qAtan2Mult = params.q_atan2_mult || 5, qAtan2Mult2 = params.q_atan2_mult2 || 9;
|
| 935 |
-
const pxSinMult = params.px_sin_mult || 60, pxAdd = params.px_add || 200;
|
| 936 |
-
const pyQAdd = params.py_q_add || 40, pyDMult = params.py_d_mult || 79;
|
| 937 |
-
const cDMult = params.c_d_mult || 1.1, cTDiv = params.c_t_div || 18;
|
| 938 |
-
const trigKSin = params.trig_k_sin || 'sin';
|
| 939 |
-
const trigKCos1 = params.trig_k_cos1 || 'cos';
|
| 940 |
-
const trigKCos2 = params.trig_k_cos2 || 'cos';
|
| 941 |
-
const trigQSin = params.trig_q_sin || 'sin';
|
| 942 |
-
const trigQAtan2 = params.trig_q_atan2 || 'atan';
|
| 943 |
-
const trigPxSin = params.trig_px_sin || 'sin';
|
| 944 |
-
const trigPySin = params.trig_py_sin || 'sin';
|
| 945 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 946 |
-
const patternCenterY = (pyDMult * 50) * zoomFactor;
|
| 947 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 948 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 30000;
|
| 949 |
-
for(let i = maxI; i > 0; i -= step){
|
| 950 |
-
let k = i < kCondThreshold ? trig(trigKSin, i / kSinDiv) * kSinMult : kCosMult * trig(trigKCos1, i / kCosDiv1) * trig(trigKCos2, i / kCosDiv2);
|
| 951 |
-
let e = i / eDiv - eSub;
|
| 952 |
-
let d = (Math.hypot(k, e) ** dPow / dDiv) + dAdd;
|
| 953 |
-
let q = k * (qKMult + trig(trigQSin, d * qSinDMult - t + k)) - qAtan2Mult * Math.atan2(k, e) * qAtan2Mult2;
|
| 954 |
-
let c = d * cDMult - t / cTDiv + (i % 2) * 3;
|
| 955 |
-
let px = (q + pxSinMult * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 956 |
-
let py = ((q + pyQAdd) * trig(trigPySin, c - d) + d * pyDMult) * zoomFactor;
|
| 957 |
-
if(isFinite(px) && isFinite(py)) {
|
| 958 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 959 |
-
}
|
| 960 |
-
}
|
| 961 |
-
} else if(currentBaseSet === 'yuruyurau_3') {
|
| 962 |
-
// Render yuruyurau #3
|
| 963 |
-
const zoomFactor = 0.3;
|
| 964 |
-
const kMult = params.k_mult || 5, kCosDiv1 = params.k_cos_div1 || 49, kCosDiv2 = params.k_cos_div2 || 3690;
|
| 965 |
-
const eDiv = params.e_div || 984, eSub = params.e_sub || 12;
|
| 966 |
-
const dPow = params.d_pow || 2, dDiv = params.d_div || 99, dAdd = params.d_add || 1;
|
| 967 |
-
const qKMult = params.q_k_mult || 4, qSinDMult = params.q_sin_d_mult || 18, qSinTMult = params.q_sin_t_mult || 2;
|
| 968 |
-
const qSinMod = params.q_sin_mod || 3, qSinModMult = params.q_sin_mod_mult || 2;
|
| 969 |
-
const qAtan2Mult = params.q_atan2_mult || 5, qAtan2Mult2 = params.q_atan2_mult2 || 9;
|
| 970 |
-
const pxSinMult = params.px_sin_mult || 30, pxAdd = params.px_add || 200;
|
| 971 |
-
const pySinMult = params.py_sin_mult || 80, pyDMult = params.py_d_mult || 79;
|
| 972 |
-
const cTDiv = params.c_t_div || 18, cMod = params.c_mod || 3, cModMult = params.c_mod_mult || 4;
|
| 973 |
-
const trigKCos1 = params.trig_k_cos1 || 'cos';
|
| 974 |
-
const trigKCos2 = params.trig_k_cos2 || 'cos';
|
| 975 |
-
const trigQSin = params.trig_q_sin || 'sin';
|
| 976 |
-
const trigQAtan2 = params.trig_q_atan2 || 'atan';
|
| 977 |
-
const trigPxSin = params.trig_px_sin || 'sin';
|
| 978 |
-
const trigPySin = params.trig_py_sin || 'sin';
|
| 979 |
-
const trigStrokeCos = params.trig_stroke_cos || 'cos';
|
| 980 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 981 |
-
const patternCenterY = (pyDMult * 50) * zoomFactor;
|
| 982 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 983 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 20000;
|
| 984 |
-
for(let i = maxI; i > 0; i -= step){
|
| 985 |
-
let k = kMult * trig(trigKCos1, i / kCosDiv1) * trig(trigKCos2, i / kCosDiv2);
|
| 986 |
-
let e = i / eDiv - eSub;
|
| 987 |
-
let d = (Math.hypot(k, e) ** dPow / dDiv) + dAdd;
|
| 988 |
-
let cosVal = trig(trigStrokeCos, t + e);
|
| 989 |
-
let stroke_r = 36 + 3 / (Math.abs(cosVal) > 0.001 ? cosVal : 0.001);
|
| 990 |
-
p.stroke(255, Math.min(255, Math.max(0, stroke_r)));
|
| 991 |
-
let c = d - t / cTDiv + (i % cMod) * cModMult;
|
| 992 |
-
let px = (k * (qKMult + trig(trigQSin, d * qSinDMult - t * qSinTMult + (i % qSinMod) * qSinModMult)) - qAtan2Mult * Math.atan2(k, e) * qAtan2Mult2 + pxSinMult * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 993 |
-
let py = (pySinMult * trig(trigPySin, c - d) + d * pyDMult) * zoomFactor;
|
| 994 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 995 |
-
}
|
| 996 |
-
*/ } else if(currentBaseSet === 'yuruyurau_4') {
|
| 997 |
-
// Render yuruyurau #4
|
| 998 |
-
p.stroke(255, 96);
|
| 999 |
-
const zoomFactor = 0.3;
|
| 1000 |
-
const kMult = params.k_mult || 5, kCosDiv = params.k_cos_div || 8;
|
| 1001 |
-
const eMult = params.e_mult || 5, eCosDiv = params.e_cos_div || 9;
|
| 1002 |
-
const dDivBase = params.d_div_base || 6, dPow = params.d_pow || 4, dAdd = params.d_add || 4;
|
| 1003 |
-
const qKMult = params.q_k_mult || 3, qEDiv = params.q_e_div || 2;
|
| 1004 |
-
const qSinMult = params.q_sin_mult || 3, qSinKDDiv = params.q_sin_kd_div || 3, qBitwiseMult = params.q_bitwise_mult || 70;
|
| 1005 |
-
const pxSinMult = params.px_sin_mult || 1, pxAdd = params.px_add || 200;
|
| 1006 |
-
const pyCosAdd1 = params.py_cos_add1 || 7, pyAdd = params.py_add || 200;
|
| 1007 |
-
const cTDiv = params.c_t_div || 9, cMod1 = params.c_mod1 || 5, cMod1Mult = params.c_mod1_mult || 5, cMod2 = params.c_mod2 || 2;
|
| 1008 |
-
const trigKCos = params.trig_k_cos || 'cos';
|
| 1009 |
-
const trigECos = params.trig_e_cos || 'cos';
|
| 1010 |
-
const trigQSin = params.trig_q_sin || 'sin';
|
| 1011 |
-
const trigQSin2 = params.trig_q_sin2 || 'sin';
|
| 1012 |
-
const trigPxSin = params.trig_px_sin || 'sin';
|
| 1013 |
-
const trigPyCos = params.trig_py_cos || 'cos';
|
| 1014 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 1015 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 1016 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 1017 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 20000;
|
| 1018 |
-
for(let i = maxI; i > 0; i -= step){
|
| 1019 |
-
let y = i / 799;
|
| 1020 |
-
let k = kMult * trig(trigKCos, i / kCosDiv);
|
| 1021 |
-
let e = eMult * trig(trigECos, y / eCosDiv);
|
| 1022 |
-
let d = (Math.hypot(k, e) / (dDivBase + (i % 5))) ** dPow + dAdd;
|
| 1023 |
-
let q = k * (qKMult + e / qEDiv * trig(trigQSin, d * d - t)) - qSinMult * trig(trigQSin2, k * d / qSinKDDiv) - (~(i & 1)) * qBitwiseMult;
|
| 1024 |
-
let c = d - t / cTDiv + (i % cMod1) * cMod1Mult + (i % cMod2);
|
| 1025 |
-
let px = (q * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 1026 |
-
let py = (q * trig(trigPyCos, c - (i % cMod2) + (i % cMod1) * 3 + pyCosAdd1) + pyAdd) * zoomFactor;
|
| 1027 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 1028 |
-
}
|
| 1029 |
-
} else {
|
| 1030 |
-
// Render "always_finding_yourself" equations
|
| 1031 |
-
p.stroke(255, 40); // Same as base preview
|
| 1032 |
-
const zoomFactor = 0.2; // Same as base
|
| 1033 |
-
const kDiv = params.k_div, kSub = params.k_sub, eDiv = params.e_div, eAdd = params.e_add ?? 9;
|
| 1034 |
-
const oDiv = params.o_div, xOff = params.x_offset, yTrigDiv = params.y_trig_div;
|
| 1035 |
-
const qMult = params.q_mult, pxAdd = params.px_add, pyAdd = params.py_add, pyQDiv = params.py_q_div;
|
| 1036 |
-
const trigY = params.trig_y, trigE = params.trig_e, trigO = params.trig_o;
|
| 1037 |
-
const trigCSin = params.trig_c_sin, trigC4Cos = params.trig_c4_cos, trigCCos = params.trig_c_cos;
|
| 1038 |
-
|
| 1039 |
-
// Pattern center in world coordinates (pxAdd and pyAdd are the base offsets)
|
| 1040 |
-
const patternCenterX = pxAdd * zoomFactor;
|
| 1041 |
-
const patternCenterY = pyAdd * zoomFactor;
|
| 1042 |
-
|
| 1043 |
-
// Same point count and step as base preview (6400 points, i += 3)
|
| 1044 |
-
const step = FRAME_SAMPLING_STEPS[currentBaseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 1045 |
-
const maxI = LOOP_BOUNDS[currentBaseSet] || 6400;
|
| 1046 |
-
for(let i=0;i<maxI;i+=step){
|
| 1047 |
-
let x=i%200, y=i/200;
|
| 1048 |
-
let k=x/kDiv-kSub; let ksafe=Math.abs(k)>1e-6?k:1e-6;
|
| 1049 |
-
let e=y/eDiv+eAdd;
|
| 1050 |
-
let o=Math.hypot(k,e)/oDiv;
|
| 1051 |
-
let q=x+xOff+y+1/ksafe + o*k*(trig(trigY,y)/yTrigDiv + trig(trigE,e)) * trig(trigO,o*4 - t);
|
| 1052 |
-
let c=o + e/99 - t/8 + (i%2)*3;
|
| 1053 |
-
let px=(qMult*q*trig(trigCSin,c) + pxAdd) * zoomFactor;
|
| 1054 |
-
let py=(pyAdd + y/2*trig(trigC4Cos,c*4 - o) - q/pyQDiv*trig(trigCCos,c - 1)) * zoomFactor;
|
| 1055 |
-
// Center the pattern in the canvas
|
| 1056 |
-
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 1057 |
-
}
|
| 1058 |
}
|
| 1059 |
};
|
| 1060 |
});
|
|
@@ -1410,6 +1181,17 @@
|
|
| 1410 |
equation += ` px = q*${getParam('trig_px_sin')}(c) + ${getParam('px_add')}\n`;
|
| 1411 |
equation += ` py = q*${getParam('trig_py_cos')}(c-i%${getParam('c_mod2')}+i%${getParam('c_mod1')}*3+${getParam('py_cos_add1')}) + ${getParam('py_add')}\n`;
|
| 1412 |
equation += ` point(px,py)`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1413 |
} else {
|
| 1414 |
// Original equations (always_finding_yourself)
|
| 1415 |
equation += 'for i in range(0, 6400, 3):\n';
|
|
@@ -1452,6 +1234,103 @@
|
|
| 1452 |
// Make download function global
|
| 1453 |
window.downloadEquation = downloadEquation;
|
| 1454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1455 |
// Initialize the application
|
| 1456 |
updateMutationProbChart(); // Initialize chart
|
| 1457 |
updateTrigProbIndicator(); // Initialize trig prob indicator
|
|
|
|
| 44 |
.btn{background:#000;color:#fff;border:1px solid #666;padding:8px 16px;margin:0;cursor:pointer;font-family:monospace;font-size:11px;text-transform:uppercase;letter-spacing:0.5px}
|
| 45 |
.btn:hover{background:#333;border-color:#999}
|
| 46 |
.btn:disabled{background:#111;color:#666;border-color:#444;cursor:not-allowed}
|
| 47 |
+
.eq-chip{font-family:monospace;font-size:8px;color:#bbb;border:1px solid #444;padding:2px 6px;cursor:grab;background:#111;user-select:none;white-space:nowrap}
|
| 48 |
+
.eq-chip:hover{border-color:#aaa;color:#fff}
|
| 49 |
+
.eq-chip:active{cursor:grabbing}
|
| 50 |
+
.eq-chip-in-zone{display:inline-flex;align-items:center;gap:4px;border:1px solid #fff;padding:2px 6px;font-size:8px;font-family:monospace;color:#fff;background:#222}
|
| 51 |
+
.eq-chip-remove{cursor:pointer;color:#999;font-size:10px;line-height:1;padding:0 1px}
|
| 52 |
+
.eq-chip-remove:hover{color:#fff}
|
| 53 |
+
#merge-drop-zone{min-height:36px;border:1px dashed #444;padding:5px;background:#000;font-size:8px;color:#555;display:flex;align-items:center;gap:6px;flex-wrap:wrap;transition:border-color 0.2s,background 0.2s}
|
| 54 |
+
#merge-drop-zone.drag-over{border-color:#fff;background:#111}
|
| 55 |
</style>
|
| 56 |
</head>
|
| 57 |
<body>
|
|
|
|
| 78 |
<!-- <option value="yuruyurau_2">yuruyurau #2</option> -->
|
| 79 |
<!-- <option value="yuruyurau_3">yuruyurau #3</option> -->
|
| 80 |
<option value="yuruyurau_4">#2</option>
|
| 81 |
+
<option value="yuruyurau_5">#3</option>
|
| 82 |
</select>
|
| 83 |
</div>
|
| 84 |
|
| 85 |
+
<div style="margin-bottom:15px;">
|
| 86 |
+
<label style="font-size:9px;color:#666;text-transform:lowercase;letter-spacing:1px;display:block;margin-bottom:5px;">merge equations:</label>
|
| 87 |
+
<div id="equation-chips" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px;">
|
| 88 |
+
<div class="eq-chip" draggable="true" data-eq="always_finding_yourself">always_finding</div>
|
| 89 |
+
<div class="eq-chip" draggable="true" data-eq="a_constant_state_of_bliss">a constant state</div>
|
| 90 |
+
<div class="eq-chip" draggable="true" data-eq="yuruyurau">#1</div>
|
| 91 |
+
<div class="eq-chip" draggable="true" data-eq="yuruyurau_4">#2</div>
|
| 92 |
+
<div class="eq-chip" draggable="true" data-eq="yuruyurau_5">#3</div>
|
| 93 |
+
</div>
|
| 94 |
+
<div id="merge-drop-zone">
|
| 95 |
+
<span id="merge-drop-hint" style="pointer-events:none;">drag to layer a second equation</span>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
<div style="margin-bottom:15px;">
|
| 100 |
<label style="font-size:9px;color:#666;text-transform:lowercase;letter-spacing:1px;display:block;margin-bottom:5px;">trigonometric mutation probability:</label>
|
| 101 |
<div style="display:flex;gap:4px;align-items:center;height:20px;">
|
|
|
|
| 214 |
|
| 215 |
<script>
|
| 216 |
let variants=[]; let selectedIds=new Set();
|
| 217 |
+
let mergedSecondarySet = null; // Secondary equation for merge mode
|
| 218 |
let currentParams = {
|
| 219 |
k_div: 4, k_sub: 10.5, e_div: 9, e_add: 9, o_div: 9, x_offset: 60,
|
| 220 |
y_trig_div: 8, q_mult: 0.7, px_add: 200, py_add: 200, py_q_div: 3,
|
|
|
|
| 224 |
let currentBaseSet = 'always_finding_yourself'; // Current base equation set
|
| 225 |
let variantSketches = []; // Track p5 instances for variants to clean them up
|
| 226 |
let sketchBaseSet = null; // Which base set the current sketches were built for
|
| 227 |
+
let sketchMergedSet = null; // Which merged secondary set the current sketches were built for
|
| 228 |
// Shared mutable param objects β p5 sketches hold references so we can update without recreation
|
| 229 |
const variantParamSlots = Array.from({length: 16}, () => ({}));
|
| 230 |
let currentMutations = []; // Track mutations for highlighting
|
|
|
|
| 248 |
'yuruyurau': 6, // for(let i = 0; i < 12000; i += 6)
|
| 249 |
'yuruyurau_2': 1, // for(let i = 30000; i > 0; i--)
|
| 250 |
'yuruyurau_3': 1, // for(let i = 20000; i > 0; i--)
|
| 251 |
+
'yuruyurau_4': 1, // for(let i = 20000; i > 0; i--)
|
| 252 |
+
'yuruyurau_5': 1 // for(let i = 20000; i > 0; i--)
|
| 253 |
};
|
| 254 |
const LOOP_BOUNDS = {
|
| 255 |
'always_finding_yourself': 6400,
|
|
|
|
| 257 |
'yuruyurau': 12000,
|
| 258 |
'yuruyurau_2': 10000,
|
| 259 |
'yuruyurau_3': 10000,
|
| 260 |
+
'yuruyurau_4': 1000,
|
| 261 |
+
'yuruyurau_5': 20000
|
| 262 |
};
|
| 263 |
|
| 264 |
// O(1) trig dispatch β avoids per-point if-else chain
|
|
|
|
| 292 |
currentBaseSet = data.current_base_set;
|
| 293 |
document.getElementById('base-equation-selector').value = currentBaseSet;
|
| 294 |
}
|
| 295 |
+
if('merged_secondary_set' in data) {
|
| 296 |
+
mergedSecondarySet = data.merged_secondary_set;
|
| 297 |
+
updateMergeZoneUI();
|
| 298 |
+
}
|
| 299 |
if(data.trig_mutation_prob !== undefined) {
|
| 300 |
trigMutationProb = data.trig_mutation_prob;
|
| 301 |
updateMutationProbChart();
|
|
|
|
| 410 |
.then(data=>{
|
| 411 |
if(data.success) {
|
| 412 |
currentBaseSet = newSet;
|
| 413 |
+
mergedSecondarySet = null; // Backend clears merge on base set change
|
| 414 |
+
updateMergeZoneUI();
|
| 415 |
loadPopulation();
|
| 416 |
} else {
|
| 417 |
alert('Error changing base equation set: '+data.error);
|
|
|
|
| 484 |
equation += ` px = q*${getTrigSpan('trig_px_sin')}(c) + ${getParamSpan('px_add')}<br>`;
|
| 485 |
equation += ` py = q*${getTrigSpan('trig_py_cos')}(c-i%${getParamSpan('c_mod2')}+i%${getParamSpan('c_mod1')}*3+${getParamSpan('py_cos_add1')}) + ${getParamSpan('py_add')}<br>`;
|
| 486 |
equation += ` point(px,py)`;
|
| 487 |
+
} else if(currentBaseSet === 'yuruyurau_5') {
|
| 488 |
+
// yuruyurau #3
|
| 489 |
+
equation += 'for i in range(20000, 0, -1):<br>';
|
| 490 |
+
equation += ` m = (i%2)*${getParamSpan('m_mult')}<br>`;
|
| 491 |
+
equation += ` k = ${getParamSpan('k_mult')}*${getTrigSpan('trig_k_cos')}(i/${getParamSpan('k_cos_div')})<br>`;
|
| 492 |
+
equation += ` e = i/${getParamSpan('e_div')} - ${getParamSpan('e_sub')}<br>`;
|
| 493 |
+
equation += ` d = mag(k,e)^${getParamSpan('d_pow')}/${getParamSpan('d_div')} + ${getParamSpan('d_add')}<br>`;
|
| 494 |
+
equation += ` q = ${getParamSpan('q_base')} - e/${getParamSpan('q_e_div')}Β·${getTrigSpan('trig_q_sin1')}(k/dΒ·${getParamSpan('q_sin1_mult')}) + k/dΒ·(${getParamSpan('q_k_mult')}+${getParamSpan('q_sin2_mult')}Β·${getTrigSpan('trig_q_sin2')}(${getTrigSpan('trig_q_sin3')}(dΒ²+e/${getParamSpan('q_sin_e_div')}-t+m)))<br>`;
|
| 495 |
+
equation += ` c = d/${getParamSpan('c_d_div')} + ${getTrigSpan('trig_c_cos')}(t-dΒ·${getParamSpan('c_d2_mult')}+m)/${getParamSpan('c_cos_div')} - t/${getParamSpan('c_t_div')} + m<br>`;
|
| 496 |
+
equation += ` px = qΒ·${getTrigSpan('trig_px_cos')}(c) + ${getParamSpan('px_add')}<br>`;
|
| 497 |
+
equation += ` py = (q+${getParamSpan('py_q_add')})Β·${getTrigSpan('trig_py_sin')}(c) + ${getParamSpan('py_add')}<br>`;
|
| 498 |
+
equation += ` point(px,py)`;
|
| 499 |
} else {
|
| 500 |
// Original equations (always_finding_yourself)
|
| 501 |
equation += 'for i in range(0, 6400, 3):<br>';
|
|
|
|
| 516 |
|
| 517 |
let basePreviewSketch = null;
|
| 518 |
|
| 519 |
+
// Extract _2_ prefixed params (secondary merged equation) into a plain param object
|
| 520 |
+
function extractMergedParams(params) {
|
| 521 |
+
const result = {};
|
| 522 |
+
for(const [k, v] of Object.entries(params)) {
|
| 523 |
+
if(k.startsWith('_2_')) result[k.slice(3)] = v;
|
| 524 |
+
}
|
| 525 |
+
return result;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
// Shared rendering function: draws one equation layer's points onto p
|
| 529 |
+
function drawEquationPoints(p, params, baseSet, t, centerX, centerY) {
|
| 530 |
+
const trig = (name, val) => getTrig(name)(val);
|
| 531 |
+
|
| 532 |
+
if(baseSet === 'a_constant_state_of_bliss') {
|
| 533 |
+
const zoomFactor = 0.4;
|
| 534 |
+
const kDiv = params.k_div || 8, kSub = params.k_sub || 11.5;
|
| 535 |
+
const eDiv = params.e_div || 8, eSub = params.e_sub || 12.5;
|
| 536 |
+
const oDiv = params.o_div || 139, dMult = params.d_mult || 10;
|
| 537 |
+
const pxDiv = params.px_div || 2, pxAdd1 = params.px_add1 || 150;
|
| 538 |
+
const pyYDiv = params.py_y_div || 9, pyDMult = params.py_d_mult || 15;
|
| 539 |
+
const pyCosMult = params.py_cos_mult || 2, pyAdd = params.py_add || 220;
|
| 540 |
+
const strokeBase = params.stroke_base || 99, strokeMult = params.stroke_mult || 99, strokePow = params.stroke_pow || 30;
|
| 541 |
+
const trigDCos = params.trig_d_cos || 'cos';
|
| 542 |
+
const trigPxSin1 = params.trig_px_sin1 || 'sin';
|
| 543 |
+
const trigPxSin2 = params.trig_px_sin2 || 'sin';
|
| 544 |
+
const trigPyCos = params.trig_py_cos || 'cos';
|
| 545 |
+
const trigPySin = params.trig_py_sin || 'sin';
|
| 546 |
+
const trigStrokeSin1 = params.trig_stroke_sin1 || 'sin';
|
| 547 |
+
const trigStrokeSin2 = params.trig_stroke_sin2 || 'sin';
|
| 548 |
+
const patternCenterX = pxAdd1 * zoomFactor, patternCenterY = pyAdd * zoomFactor;
|
| 549 |
+
const step = FRAME_SAMPLING_STEPS[baseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 550 |
+
const maxI = LOOP_BOUNDS[baseSet] || 6400;
|
| 551 |
+
for(let i = 0; i < maxI; i += step){
|
| 552 |
+
let x = i % 200, y = i / 200;
|
| 553 |
+
let k = x / kDiv - kSub, e = y / eDiv - eSub;
|
| 554 |
+
let o = Math.hypot(k, e) ** 2 / oDiv;
|
| 555 |
+
let d = dMult * trig(trigDCos, o);
|
| 556 |
+
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 557 |
+
let stroke_r = strokeBase + strokeMult / trig(trigStrokeSin1, ksafe) * trig(trigStrokeSin2, t + e) ** strokePow;
|
| 558 |
+
p.stroke(stroke_r, 66);
|
| 559 |
+
let px = ((x + trig(trigPxSin1, d) * d * k) / pxDiv + pxAdd1 + o * k * trig(trigPxSin2, t + d * o)) * zoomFactor;
|
| 560 |
+
let py = (y / pyYDiv - d * pyDMult - trig(trigPyCos, d * pyCosMult) * d + pyAdd + d * trig(trigPySin, d - t)) * zoomFactor;
|
| 561 |
+
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 562 |
+
}
|
| 563 |
+
} else if(baseSet === 'yuruyurau') {
|
| 564 |
+
p.stroke(255, 96);
|
| 565 |
+
const zoomFactor = 0.3;
|
| 566 |
+
const kBase = params.k_base || 4, kSinMult = params.k_sin_mult || 3, kCosDiv = params.k_cos_div || 29;
|
| 567 |
+
const eDiv = params.e_div || 8, eSub = params.e_sub || 13;
|
| 568 |
+
const qSin1Mult = params.q_sin1_mult || 3, qSin1Mult2 = params.q_sin1_mult2 || 2, qDiv = params.q_div || 0.3;
|
| 569 |
+
const qSin2Div = params.q_sin2_div || 25, qBase = params.q_base || 9;
|
| 570 |
+
const qSin3Mult = params.q_sin3_mult || 4, qSin3EMult = params.q_sin3_e_mult || 9;
|
| 571 |
+
const qSin3DMult = params.q_sin3_d_mult || 3, qSin3TMult = params.q_sin3_t_mult || 2;
|
| 572 |
+
const pxCosMult = params.px_cos_mult || 30, pxAdd = params.px_add || 200;
|
| 573 |
+
const pyDMult = params.py_d_mult || 39, pySub = params.py_sub || 220;
|
| 574 |
+
const trigKSin = params.trig_k_sin || 'sin', trigKCos = params.trig_k_cos || 'cos';
|
| 575 |
+
const trigQSin1 = params.trig_q_sin1 || 'sin', trigQSin2 = params.trig_q_sin2 || 'sin';
|
| 576 |
+
const trigQSin3 = params.trig_q_sin3 || 'sin', trigPxCos = params.trig_px_cos || 'cos';
|
| 577 |
+
const trigPySin = params.trig_py_sin || 'sin';
|
| 578 |
+
const patternCenterX = pxAdd * zoomFactor, patternCenterY = (pySub / 2) * zoomFactor;
|
| 579 |
+
const step = FRAME_SAMPLING_STEPS[baseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 580 |
+
const maxI = LOOP_BOUNDS[baseSet] || 10000;
|
| 581 |
+
for(let i = 0; i < maxI; i += step){
|
| 582 |
+
let x = i, y = i / 235;
|
| 583 |
+
let k = (kBase + trig(trigKSin, y * 2 - t) * kSinMult) * trig(trigKCos, x / kCosDiv);
|
| 584 |
+
let e = y / eDiv - eSub, d = Math.hypot(k, e);
|
| 585 |
+
let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 586 |
+
let q = qSin1Mult * trig(trigQSin1, k * qSin1Mult2) + qDiv / ksafe + trig(trigQSin2, y / qSin2Div) * k * (qBase + qSin3Mult * trig(trigQSin3, e * qSin3EMult - d * qSin3DMult + t * qSin3TMult));
|
| 587 |
+
let c = d - t;
|
| 588 |
+
let px = (q + pxCosMult * trig(trigPxCos, c) + pxAdd) * zoomFactor;
|
| 589 |
+
let py = (q * trig(trigPySin, c) + d * pyDMult - pySub) * zoomFactor;
|
| 590 |
+
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 591 |
+
}
|
| 592 |
+
} else if(baseSet === 'yuruyurau_4') {
|
| 593 |
+
p.stroke(255, 96);
|
| 594 |
+
const zoomFactor = 0.3;
|
| 595 |
+
const kMult = params.k_mult || 5, kCosDiv = params.k_cos_div || 8;
|
| 596 |
+
const eMult = params.e_mult || 5, eCosDiv = params.e_cos_div || 9;
|
| 597 |
+
const dDivBase = params.d_div_base || 6, dPow = params.d_pow || 4, dAdd = params.d_add || 4;
|
| 598 |
+
const qKMult = params.q_k_mult || 3, qEDiv = params.q_e_div || 2;
|
| 599 |
+
const qSinMult = params.q_sin_mult || 3, qSinKDDiv = params.q_sin_kd_div || 3, qBitwiseMult = params.q_bitwise_mult || 70;
|
| 600 |
+
const pxSinMult = params.px_sin_mult || 1, pxAdd = params.px_add || 200;
|
| 601 |
+
const pyCosAdd1 = params.py_cos_add1 || 7, pyAdd = params.py_add || 200;
|
| 602 |
+
const cTDiv = params.c_t_div || 9, cMod1 = params.c_mod1 || 5, cMod1Mult = params.c_mod1_mult || 5, cMod2 = params.c_mod2 || 2;
|
| 603 |
+
const trigKCos = params.trig_k_cos || 'cos', trigECos = params.trig_e_cos || 'cos';
|
| 604 |
+
const trigQSin = params.trig_q_sin || 'sin', trigQSin2 = params.trig_q_sin2 || 'sin';
|
| 605 |
+
const trigPxSin = params.trig_px_sin || 'sin', trigPyCos = params.trig_py_cos || 'cos';
|
| 606 |
+
const patternCenterX = pxAdd * zoomFactor, patternCenterY = pyAdd * zoomFactor;
|
| 607 |
+
const step = FRAME_SAMPLING_STEPS[baseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 608 |
+
const maxI = LOOP_BOUNDS[baseSet] || 20000;
|
| 609 |
+
for(let i = maxI; i > 0; i -= step){
|
| 610 |
+
let y = i / 799;
|
| 611 |
+
let k = kMult * trig(trigKCos, i / kCosDiv);
|
| 612 |
+
let e = eMult * trig(trigECos, y / eCosDiv);
|
| 613 |
+
let d = (Math.hypot(k, e) / (dDivBase + (i % 5))) ** dPow + dAdd;
|
| 614 |
+
let q = k * (qKMult + e / qEDiv * trig(trigQSin, d * d - t)) - qSinMult * trig(trigQSin2, k * d / qSinKDDiv) - (~(i & 1)) * qBitwiseMult;
|
| 615 |
+
let c = d - t / cTDiv + (i % cMod1) * cMod1Mult + (i % cMod2);
|
| 616 |
+
let px = (q * trig(trigPxSin, c) + pxAdd) * zoomFactor;
|
| 617 |
+
let py = (q * trig(trigPyCos, c - (i % cMod2) + (i % cMod1) * 3 + pyCosAdd1) + pyAdd) * zoomFactor;
|
| 618 |
+
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 619 |
+
}
|
| 620 |
+
} else if(baseSet === 'yuruyurau_5') {
|
| 621 |
+
// a=(m,d=mag(k=9*cos(i/61),e=i/692-13)**2/99+1)=>point(
|
| 622 |
+
// (q=79-e/2*sin(k/d*4)+k/d*(8+5*sin(sin(d*d+e/9-t+m))))*cos(c=d/2+cos(t-d*2+m)/9-t/16+m)+200,
|
| 623 |
+
// (q+40)*sin(c)+200)
|
| 624 |
+
p.stroke(255, 96);
|
| 625 |
+
const zoomFactor = 0.315;
|
| 626 |
+
const kMult = params.k_mult || 9, kCosDiv = params.k_cos_div || 61;
|
| 627 |
+
const eDiv = params.e_div || 692, eSub = params.e_sub || 13;
|
| 628 |
+
const dPow = params.d_pow || 2, dDiv = params.d_div || 99, dAdd = params.d_add || 1;
|
| 629 |
+
const mMult = params.m_mult || 9;
|
| 630 |
+
const qBase = params.q_base || 79, qEDiv = params.q_e_div || 2, qSin1Mult = params.q_sin1_mult || 4;
|
| 631 |
+
const qKMult = params.q_k_mult || 8, qSin2Mult = params.q_sin2_mult || 5, qSinEDiv = params.q_sin_e_div || 9;
|
| 632 |
+
const cDDiv = params.c_d_div || 2, cD2Mult = params.c_d2_mult || 2, cCosDiv = params.c_cos_div || 9, cTDiv = params.c_t_div || 16;
|
| 633 |
+
const pxAdd = params.px_add || 200, pyQAdd = params.py_q_add || 40, pyAdd = params.py_add || 200;
|
| 634 |
+
const trigKCos = params.trig_k_cos || 'cos';
|
| 635 |
+
const trigQSin1 = params.trig_q_sin1 || 'sin';
|
| 636 |
+
const trigQSin2 = params.trig_q_sin2 || 'sin';
|
| 637 |
+
const trigQSin3 = params.trig_q_sin3 || 'sin';
|
| 638 |
+
const trigCCos = params.trig_c_cos || 'cos';
|
| 639 |
+
const trigPxCos = params.trig_px_cos || 'cos';
|
| 640 |
+
const trigPySin = params.trig_py_sin || 'sin';
|
| 641 |
+
const patternCenterX = pxAdd * zoomFactor, patternCenterY = pyAdd * zoomFactor;
|
| 642 |
+
const step = FRAME_SAMPLING_STEPS[baseSet] || 1;
|
| 643 |
+
const maxI = LOOP_BOUNDS[baseSet] || 20000;
|
| 644 |
+
for(let i = maxI; i--;) {
|
| 645 |
+
let m = (i % 2) * mMult;
|
| 646 |
+
let k = kMult * trig(trigKCos, i / kCosDiv);
|
| 647 |
+
let e = i / eDiv - eSub;
|
| 648 |
+
let d = Math.hypot(k, e) ** dPow / dDiv + dAdd;
|
| 649 |
+
let q = qBase - e / qEDiv * trig(trigQSin1, k / d * qSin1Mult) + k / d * (qKMult + qSin2Mult * trig(trigQSin2, trig(trigQSin3, d * d + e / qSinEDiv - t + m)));
|
| 650 |
+
let c = d / cDDiv + trig(trigCCos, t - d * cD2Mult + m) / cCosDiv - t / cTDiv + m;
|
| 651 |
+
let px = (q * trig(trigPxCos, c) + pxAdd) * zoomFactor;
|
| 652 |
+
let py = ((q + pyQAdd) * trig(trigPySin, c) + pyAdd) * zoomFactor;
|
| 653 |
+
if(isFinite(px) && isFinite(py)) p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 654 |
+
}
|
| 655 |
+
} else {
|
| 656 |
+
// always_finding_yourself
|
| 657 |
+
p.stroke(255, 40);
|
| 658 |
+
const zoomFactor = 0.2;
|
| 659 |
+
const kDiv = params.k_div, kSub = params.k_sub, eDiv = params.e_div, eAdd = params.e_add ?? 9;
|
| 660 |
+
const oDiv = params.o_div, xOff = params.x_offset, yTrigDiv = params.y_trig_div;
|
| 661 |
+
const qMult = params.q_mult, pxAdd = params.px_add, pyAdd = params.py_add, pyQDiv = params.py_q_div;
|
| 662 |
+
const trigY = params.trig_y, trigE = params.trig_e, trigO = params.trig_o;
|
| 663 |
+
const trigCSin = params.trig_c_sin, trigC4Cos = params.trig_c4_cos, trigCCos = params.trig_c_cos;
|
| 664 |
+
const patternCenterX = pxAdd * zoomFactor, patternCenterY = pyAdd * zoomFactor;
|
| 665 |
+
const step = FRAME_SAMPLING_STEPS[baseSet] || DEFAULT_FRAME_SAMPLING_STEP;
|
| 666 |
+
const maxI = LOOP_BOUNDS[baseSet] || 6400;
|
| 667 |
+
for(let i = 0; i < maxI; i += step){
|
| 668 |
+
let x = i % 200, y = i / 200;
|
| 669 |
+
let k = x / kDiv - kSub; let ksafe = Math.abs(k) > 1e-6 ? k : 1e-6;
|
| 670 |
+
let e = y / eDiv + eAdd, o = Math.hypot(k, e) / oDiv;
|
| 671 |
+
let q = x + xOff + y + 1/ksafe + o * k * (trig(trigY, y) / yTrigDiv + trig(trigE, e)) * trig(trigO, o * 4 - t);
|
| 672 |
+
let c = o + e / 99 - t / 8 + (i % 2) * 3;
|
| 673 |
+
let px = (qMult * q * trig(trigCSin, c) + pxAdd) * zoomFactor;
|
| 674 |
+
let py = (pyAdd + y / 2 * trig(trigC4Cos, c * 4 - o) - q / pyQDiv * trig(trigCCos, c - 1)) * zoomFactor;
|
| 675 |
+
p.point(px - patternCenterX + centerX, py - patternCenterY + centerY);
|
| 676 |
+
}
|
| 677 |
+
}
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
function updateBaseEquationDisplay(){
|
| 681 |
const container = document.getElementById('base-preview-canvas');
|
| 682 |
|
|
|
|
| 689 |
const baseParams = {...currentParams};
|
| 690 |
|
| 691 |
basePreviewSketch = new p5(p => {
|
|
|
|
|
|
|
| 692 |
p.setup = () => {
|
| 693 |
+
p.createCanvas(126, 126).parent(container);
|
| 694 |
p.pixelDensity(1);
|
| 695 |
p.frameRate(FRAME_RATE);
|
| 696 |
p.noFill();
|
|
|
|
| 698 |
};
|
| 699 |
|
| 700 |
p.draw = () => {
|
|
|
|
| 701 |
const frame = p.frameCount % ANIMATION_FRAMES;
|
| 702 |
+
const t = (frame / ANIMATION_FRAMES) * Math.PI * 2 * 100;
|
| 703 |
+
p.clear();
|
| 704 |
+
const centerX = p.width / 2, centerY = p.height / 2;
|
| 705 |
+
drawEquationPoints(p, baseParams, currentBaseSet, t, centerX, centerY);
|
| 706 |
+
if(mergedSecondarySet) {
|
| 707 |
+
drawEquationPoints(p, extractMergedParams(baseParams), mergedSecondarySet, t, centerX, centerY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 708 |
}
|
| 709 |
};
|
| 710 |
});
|
|
|
|
| 754 |
currentMutations = [];
|
| 755 |
variants.forEach(v => { if(v.mutations) currentMutations.push(...v.mutations); });
|
| 756 |
|
| 757 |
+
// Fast path: same base set + same merge + same count β just update params & labels, no sketch recreation
|
| 758 |
+
if(sketchBaseSet === currentBaseSet && sketchMergedSet === mergedSecondarySet && variantSketches.length === variants.length) {
|
| 759 |
variants.forEach((v, i) => {
|
| 760 |
// Update shared param slot β the p5 draw loop reads from this reference each frame
|
| 761 |
Object.assign(variantParamSlots[i], v.params);
|
|
|
|
| 772 |
return;
|
| 773 |
}
|
| 774 |
|
| 775 |
+
// Full rebuild: base set/merge changed or first render
|
| 776 |
variantSketches.forEach(s => { if(s && typeof s.remove === 'function') s.remove(); });
|
| 777 |
variantSketches = [];
|
| 778 |
sketchBaseSet = currentBaseSet;
|
| 779 |
+
sketchMergedSet = mergedSecondarySet;
|
| 780 |
grid.innerHTML = '';
|
| 781 |
|
| 782 |
variants.forEach((v, i) => {
|
|
|
|
| 809 |
|
| 810 |
function renderEquation(container, params){
|
| 811 |
const sketch = new p5(p=>{
|
|
|
|
|
|
|
|
|
|
| 812 |
p.setup=()=>{
|
| 813 |
p.createCanvas(126,126).parent(container);
|
| 814 |
p.pixelDensity(1);
|
|
|
|
| 819 |
};
|
| 820 |
|
| 821 |
p.draw=()=>{
|
|
|
|
| 822 |
const frame = p.frameCount % ANIMATION_FRAMES;
|
| 823 |
+
const t = (frame / ANIMATION_FRAMES) * Math.PI * 2 * 100;
|
|
|
|
| 824 |
p.background(0);
|
| 825 |
+
const centerX = p.width / 2, centerY = p.height / 2;
|
| 826 |
+
drawEquationPoints(p, params, currentBaseSet, t, centerX, centerY);
|
| 827 |
+
if(mergedSecondarySet) {
|
| 828 |
+
drawEquationPoints(p, extractMergedParams(params), mergedSecondarySet, t, centerX, centerY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
}
|
| 830 |
};
|
| 831 |
});
|
|
|
|
| 1181 |
equation += ` px = q*${getParam('trig_px_sin')}(c) + ${getParam('px_add')}\n`;
|
| 1182 |
equation += ` py = q*${getParam('trig_py_cos')}(c-i%${getParam('c_mod2')}+i%${getParam('c_mod1')}*3+${getParam('py_cos_add1')}) + ${getParam('py_add')}\n`;
|
| 1183 |
equation += ` point(px,py)`;
|
| 1184 |
+
} else if(baseSet === 'yuruyurau_5') {
|
| 1185 |
+
equation += 'for i in range(20000, 0, -1):\n';
|
| 1186 |
+
equation += ` m = (i%2) * ${getParam('m_mult')}\n`;
|
| 1187 |
+
equation += ` k = ${getParam('k_mult')}*${getParam('trig_k_cos')}(i/${getParam('k_cos_div')})\n`;
|
| 1188 |
+
equation += ` e = i/${getParam('e_div')} - ${getParam('e_sub')}\n`;
|
| 1189 |
+
equation += ` d = mag(k,e)^${getParam('d_pow')}/${getParam('d_div')} + ${getParam('d_add')}\n`;
|
| 1190 |
+
equation += ` q = ${getParam('q_base')} - e/${getParam('q_e_div')}*${getParam('trig_q_sin1')}(k/d*${getParam('q_sin1_mult')}) + k/d*(${getParam('q_k_mult')}+${getParam('q_sin2_mult')}*${getParam('trig_q_sin2')}(${getParam('trig_q_sin3')}(d*d+e/${getParam('q_sin_e_div')}-t+m)))\n`;
|
| 1191 |
+
equation += ` c = d/${getParam('c_d_div')} + ${getParam('trig_c_cos')}(t-d*${getParam('c_d2_mult')}+m)/${getParam('c_cos_div')} - t/${getParam('c_t_div')} + m\n`;
|
| 1192 |
+
equation += ` px = q*${getParam('trig_px_cos')}(c) + ${getParam('px_add')}\n`;
|
| 1193 |
+
equation += ` py = (q+${getParam('py_q_add')})*${getParam('trig_py_sin')}(c) + ${getParam('py_add')}\n`;
|
| 1194 |
+
equation += ` point(px,py)`;
|
| 1195 |
} else {
|
| 1196 |
// Original equations (always_finding_yourself)
|
| 1197 |
equation += 'for i in range(0, 6400, 3):\n';
|
|
|
|
| 1234 |
// Make download function global
|
| 1235 |
window.downloadEquation = downloadEquation;
|
| 1236 |
|
| 1237 |
+
// ββ Merge zone logic ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1238 |
+
|
| 1239 |
+
const EQ_LABELS = {
|
| 1240 |
+
'always_finding_yourself': 'always_finding',
|
| 1241 |
+
'a_constant_state_of_bliss': 'a constant state',
|
| 1242 |
+
'yuruyurau': '#1',
|
| 1243 |
+
'yuruyurau_4': '#2',
|
| 1244 |
+
'yuruyurau_5': '#3'
|
| 1245 |
+
};
|
| 1246 |
+
|
| 1247 |
+
function updateMergeZoneUI() {
|
| 1248 |
+
const zone = document.getElementById('merge-drop-zone');
|
| 1249 |
+
const hint = document.getElementById('merge-drop-hint');
|
| 1250 |
+
if(!zone) return;
|
| 1251 |
+
// Clear dynamic chips (keep the hint span)
|
| 1252 |
+
zone.querySelectorAll('.eq-chip-in-zone').forEach(el => el.remove());
|
| 1253 |
+
if(mergedSecondarySet) {
|
| 1254 |
+
if(hint) hint.style.display = 'none';
|
| 1255 |
+
const chip = document.createElement('div');
|
| 1256 |
+
chip.className = 'eq-chip-in-zone';
|
| 1257 |
+
chip.innerHTML = `${EQ_LABELS[mergedSecondarySet] || mergedSecondarySet}<span class="eq-chip-remove" onclick="clearMerge()" title="remove">β</span>`;
|
| 1258 |
+
zone.appendChild(chip);
|
| 1259 |
+
} else {
|
| 1260 |
+
if(hint) hint.style.display = '';
|
| 1261 |
+
}
|
| 1262 |
+
}
|
| 1263 |
+
|
| 1264 |
+
function setMergedSecondary(setName) {
|
| 1265 |
+
if(setName === currentBaseSet) {
|
| 1266 |
+
// Can't merge an equation with itself as secondary β silently ignore
|
| 1267 |
+
return;
|
| 1268 |
+
}
|
| 1269 |
+
fetchJSON('/api/set_merged_secondary', {
|
| 1270 |
+
method: 'POST',
|
| 1271 |
+
headers: {'Content-Type': 'application/json'},
|
| 1272 |
+
body: JSON.stringify({set_name: setName})
|
| 1273 |
+
})
|
| 1274 |
+
.then(data => {
|
| 1275 |
+
if(data.success) {
|
| 1276 |
+
mergedSecondarySet = data.merged_secondary_set;
|
| 1277 |
+
updateMergeZoneUI();
|
| 1278 |
+
loadPopulation();
|
| 1279 |
+
}
|
| 1280 |
+
})
|
| 1281 |
+
.catch(err => console.error('Error setting merge:', err));
|
| 1282 |
+
}
|
| 1283 |
+
|
| 1284 |
+
function clearMerge() {
|
| 1285 |
+
fetchJSON('/api/set_merged_secondary', {
|
| 1286 |
+
method: 'POST',
|
| 1287 |
+
headers: {'Content-Type': 'application/json'},
|
| 1288 |
+
body: JSON.stringify({set_name: null})
|
| 1289 |
+
})
|
| 1290 |
+
.then(data => {
|
| 1291 |
+
if(data.success) {
|
| 1292 |
+
mergedSecondarySet = null;
|
| 1293 |
+
updateMergeZoneUI();
|
| 1294 |
+
loadPopulation();
|
| 1295 |
+
}
|
| 1296 |
+
})
|
| 1297 |
+
.catch(err => console.error('Error clearing merge:', err));
|
| 1298 |
+
}
|
| 1299 |
+
window.clearMerge = clearMerge;
|
| 1300 |
+
|
| 1301 |
+
// Set up draggable equation chips
|
| 1302 |
+
document.querySelectorAll('.eq-chip').forEach(chip => {
|
| 1303 |
+
chip.addEventListener('dragstart', e => {
|
| 1304 |
+
e.dataTransfer.setData('eq-set', chip.dataset.eq);
|
| 1305 |
+
e.dataTransfer.effectAllowed = 'copy';
|
| 1306 |
+
});
|
| 1307 |
+
});
|
| 1308 |
+
|
| 1309 |
+
// Set up merge drop zone
|
| 1310 |
+
const mergeDropZone = document.getElementById('merge-drop-zone');
|
| 1311 |
+
if(mergeDropZone) {
|
| 1312 |
+
mergeDropZone.addEventListener('dragover', e => {
|
| 1313 |
+
if(e.dataTransfer.types.includes('eq-set')) {
|
| 1314 |
+
e.preventDefault();
|
| 1315 |
+
e.dataTransfer.dropEffect = 'copy';
|
| 1316 |
+
mergeDropZone.classList.add('drag-over');
|
| 1317 |
+
}
|
| 1318 |
+
});
|
| 1319 |
+
mergeDropZone.addEventListener('dragleave', () => mergeDropZone.classList.remove('drag-over'));
|
| 1320 |
+
mergeDropZone.addEventListener('drop', e => {
|
| 1321 |
+
e.preventDefault();
|
| 1322 |
+
mergeDropZone.classList.remove('drag-over');
|
| 1323 |
+
const setName = e.dataTransfer.getData('eq-set');
|
| 1324 |
+
if(setName) setMergedSecondary(setName);
|
| 1325 |
+
});
|
| 1326 |
+
}
|
| 1327 |
+
|
| 1328 |
+
// Also reset sketchBaseSet when merge changes so renderVariants does a full rebuild
|
| 1329 |
+
const _origSetMergedSecondary = setMergedSecondary;
|
| 1330 |
+
// (sketchBaseSet reset happens via loadPopulation β renderVariants which checks sketchBaseSet)
|
| 1331 |
+
|
| 1332 |
+
// ββ End merge zone logic ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1333 |
+
|
| 1334 |
// Initialize the application
|
| 1335 |
updateMutationProbChart(); // Initialize chart
|
| 1336 |
updateTrigProbIndicator(); // Initialize trig prob indicator
|