-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathchaos-game-3d.html
More file actions
184 lines (167 loc) · 9.94 KB
/
chaos-game-3d.html
File metadata and controls
184 lines (167 loc) · 9.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Play around with the 3D chaos game</title>
<link rel="stylesheet" href="static/chaos-game.css?v=a82c764f0033">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nouislider@14.6.3/distribute/nouislider.min.css">
<script src="https://cdn.jsdelivr.net/npm/nouislider@14.6.3/distribute/nouislider.min.js"></script>
</head>
<body>
<div class="header-container">
<h1>Play around with the <a href="https://www.youtube.com/watch?v=kbKtFN71Lfs">Chaos</a> <a href="https://en.wikipedia.org/wiki/Chaos_game">game</a> in 3D!</h1>
<div class="button-grid">
<button id="generateBtn" aria-label="Generate new point cloud" title="Generate new point cloud">New point cloud</button>
<button id="generateAddBtn" aria-label="Add more points to existing cloud" title="Add more points to existing cloud">Add points</button>
<button id="stopBtn" style="display: none;" aria-label="Stop generation" title="Stop generation">Stop</button>
</div>
<span>
<button id="shareBtn" aria-label="Share current configuration" title="Share current configuration">Share</button>
<button id="resetSlidersBtn" style="display: none;" aria-label="Reset sliders to default values" title="Reset sliders to default values">Reset sliders ⟲</button>
</span>
</div>
<div id="sliders"></div>
<div id="controls"></div>
<div id="progress-indicator"></div>
<div id="threejs-container">
<button id="resetCameraBtn" aria-label="Reset camera view" title="Reset camera view">⟲</button>
<div id="controls3d">
<strong>3D Controls:</strong><br>
<input type="checkbox" id="scaffolding-toggle" checked>
<label for="scaffolding-toggle">Show scaffolding</label><br>
• Orbit: Left click + drag<br>
• Zoom: Mouse wheel<br>
• Pan: Right click + drag
</div>
</div>
<details id="customizeMathJSCode">
<summary>Customize the "next point" code</summary>
<div id="codeExplanation">
Write your own code to calculate where the next point should be, by setting the <code>nextPoint</code> variable to vector, ex. <code>nextPoint = [100, 200, 300]</code>. The default code simply picks one of the targets at random and moves the current point halfway there, but you can change that using <a href="https://mathjs.org/docs/reference/functions.html#arithmetic-functions">functions</a>, <a href="https://mathjs.org/docs/expressions/syntax.html#operators">operators</a>, and whatever else you can find in <a href="https://mathjs.org/docs/">MathJS</a>. You can use the coordinates of the current point, <code>currentPoint</code>, and the coordinates of each target, <code>targetPoints</code>. Both are matrices.<br/><br/>
In the default initialization code you will see uses of the <code>createSlider()</code> function. This creates a slider that controls a number. This function should usually be used in the initialization code. Example: <code>t = createSlider("some number", -1, 4, 2)</code> will create a slider ranging over -1 to 4 and starting at 2. Then ex. <code>nextPoint = currentPoint + t</code> in your code gives your end user 'live' control over the scalar value, <code>t</code>.<br/><br/>
<details class="examples-docs">
<summary>Show examples</summary>
<div>
<h4>Examples:</h4>
<div class="codeBlock">
Add 1 to x, y, and z:
<pre class="codeExample">
nextPoint = currentPoint + 1</pre>
</div>
<div class="codeBlock">
Go to midpoint, then rotate around z axis:
<pre class="codeExample">
nextTargetIndex = math.randomInt(1, targetPointsLength+1)
nextTarget = targetPoints[nextTargetIndex, :]
midpoint = (currentPoint + nextTarget) / 2
# Rotation around Z axis
rotation = [[math.cos(math.pi/4), -math.sin(math.pi/4), 0],
[math.sin(math.pi/4), math.cos(math.pi/4), 0],
[0, 0, 1]]
nextPoint = math.multiply(midpoint, rotation)</pre>
</div>
</div>
</details>
</div>
<details class="advanced-stuff">
<summary>Show advanced</summary>
<div>
<ul>
<li>
<strong>write()</strong> Write text to the page. Example: <code>write("currentPoint:", currentPoint)</code>
</li>
<li>
<strong>Advanced use of <code>createSlider()</code></strong> Optionally you can specifiy a 5th parameter which, when <code>false</code>, prevents the existing points from being cleared when the user slides the slider. Example: <code>slider = createSlider("rotation", 0, pi*2, 0, false)</code>. You can also pass arbitrary arguments to <a href="https://refreshless.com/nouislider/">noUiSlider.create()</a> by using a Javascript object as the 5th parameter, like <code>{step: 0.01}</code> to set a custom step increment.
</li>
<li>
<strong>Saving data for the next iteration</strong> Variables set in one iteration are available in subsequent iterations. See "Avoid previous target" in the advanced examples, below.
</li>
<li>
<strong>opacity</strong> Setting this variable controls point transparency. Range: 0.0 (fully transparent) to 1.0 (fully opaque). Lower values help visualize point density. Example: <code>opacity = 0.3</code>.
</li>
<li>
<strong>pointsCount</strong> Setting this variable in the initialization code controls the total number of points to generate. Higher values produce more detailed fractals. Example: <code>pointsCount = 2000000</code>.
</li>
<li>
<strong>targetsCount</strong> Setting this variable in the initialization code controls the number of target vertices in <code>targetPoints</code>. Default targets are distributed evenly on the unit sphere. Example: <code>targetsCount = 6</code>.
</li>
<li>
<strong>nextPointColor</strong> If you set this variable to, ex. <a href="https://en.wikipedia.org/wiki/RGB_color_model">[255, 0, 0]</a>, the next point will be plotted in red. Example: <code>nextPointColor = [111, 200, 15]</code>. You can optionally add a fourth element, the opacity (aka alpha), a number between 0 and 1. Example: <code>nextPointColor = [111, 200, 15, 0.3]</code>. The default opacity is 1.0.
</li>
</ul>
<div class="advanced-examples">
<div class="codeBlock">
Advanced example - Avoid previous target:
<pre class="codeExample">
# IMPORTANT! Put these two lines in the "Initialization code" pane.
previous_target = -1 # start with a non-existing target
slider = createSlider("how far to go toward the target", -2, 1.4, 0.5)
# Put the rest of the code in the main pane
# Get all possible indices except the target we used in the last iteration
possibleTargetIndices = math.range(1, targetPointsLength+1)
possibleTargetIndices = possibleTargetIndices[possibleTargetIndices != previous_target]
nextTargetIndex = math.pickRandom(possibleTargetIndices)
nextTarget = targetPoints[nextTargetIndex, :]
nextPoint = currentPoint + (nextTarget - currentPoint) * slider
# Store current target for next iteration
previous_target = nextTargetIndex</pre>
</div>
<div class="codeBlock">
Advanced example - The Corcoran Attractor:
<pre class="codeExample">
# Put this chunk in the "Initialization code" pane.
currentPoint = [100, 100, 100] # arbitrary starting point
targetsCount = createSlider("Targets", 1, 16, 6, {step: 1, display: "inline"})
pointsCount = createSlider("Points", 0, 300000, 100000, {step: 1, display: "inline"})
opacity = createSlider("Opacity (0.0 - 1.0)", .01, 1, 1, {step: 0.01, display: "inline"})
phase_offset = createSlider("Phase offset", 0, 2*math.pi, 5/2)
amplitude = createSlider("Amplitude", -3/2, 4, 1/2, {display: "inline"})
bias = createSlider("Bias", -1, 7/4, 1/2, {display: "inline"})
# Put the rest of the code in the main pane
nextTargetIndex = math.randomInt(1, targetPointsLength+1)
nextTarget = targetPoints[nextTargetIndex, :]
displacement_factors = math.matrix([math.sin(nextTarget[2] + phase_offset) * amplitude + bias, math.sin(nextTarget[1] + phase_offset) * amplitude + bias, math.sin(nextTarget[3] + phase_offset) * amplitude + bias])
# Apply the factors to the displacement
displacement_to_target = nextTarget - currentPoint
nextPoint = currentPoint + math.dotMultiply(displacement_to_target, displacement_factors)
</pre>
</div>
</div>
</div>
</details>
<br/>
<label for="initializationMathJSCode">Initialization code (runs once before first iteration):</label>
<textarea class="mathJSCode" id="initializationMathJSCode" rows="4">
currentPoint = [100, 100, 100] # arbitrary starting point
targetsCount = createSlider("Targets", 1, 16, 4, {step: 1, display: "inline"})
pointsCount = createSlider("Points", 0, 300000, 100000, {step: 1, display: "inline"})
opacity = createSlider("Opacity (0.0 - 1.0)", .01, 1, 1, {step: 0.01, display: "inline"})
howFar = createSlider("How far to go toward the target", 0, 1.4, 0.5) # a slider to let the user control how far to go toward the target
</textarea>
<br/>
<br/>
<label for="nextVertexAndPointMathJSCode">Code to calculate the next point (runs each iteration):</label>
<textarea class="mathJSCode" id="nextVertexAndPointMathJSCode" rows="10">
# pick a random target vertex
nextTargetIndex = math.randomInt(1, targetPointsLength+1)
# ... and retrieve its coordinates
nextTarget = targetPoints[nextTargetIndex, :]
# calculate the vector from currentPoint to nextTarget
delta = nextTarget - currentPoint
# scale that vector
scaledDelta = delta * howFar
# set next point to be scaledDelta away from currentPoint
nextPoint = currentPoint + scaledDelta
</textarea>
<div id="debugModeDiv">
<input type="checkbox" id="debugMode" />
<label for="debugMode" class="child-checkbox-label">Debug mode (slow, shows error messages)</label>
</div>
</details>
<div id="errorMessage">
</div>
<script type="module" src="static/chaos-game-3d.js?v=d6db03bb"></script>
<div><small><a href="https://github.com/herdrick/chaos-game/">source</a> <a href="chaos-game.html">2D version</a></small></div>
<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "9ded2ae934bc48fcb280531fe7b8e22f"}'></script>
</body>
</html>