Play around with the Chaos game in 3D!

3D Controls:

• Orbit: Left click + drag
• Zoom: Mouse wheel
• Pan: Right click + drag
Customize the "next point" code
Write your own code to calculate where the next point should be, by setting the nextPoint variable. nextPoint must be an array of three numbers [100, 200, 300] or a matrix [[100, 200, 300]]. The default code simply picks one of the targets at random and moves the current point halfway there, but you can change that using functions, operators, and whatever else you can find in MathJS. You can use the coordinates of the current point, currentPoint, and the coordinates of each target, targetPoints. Both are matrices.
Show examples

Examples:

Add 1 to x, y, and z:
nextPoint = currentPoint + 1
Go to midpoint, then rotate around z axis:
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)
Show advanced
  • write() function Write text to the page. Example: write("currentPoint:", currentPoint)
  • nextPointColor If you set the variable nextPointColor to [255, 0, 0] the next point will be plotted in red. Example: nextPointColor = [111, 200, 15]. You can optionally add a fourth element, the opacity (aka alpha), a number between 0 and 1. Example: nextPointColor = [111, 200, 15, 0.3]. The default opacity is 1.0.
  • Technique: saving data for the next iteration Variables set in one iteration are available in subsequent iterations. See "Avoid previous target" in the advanced examples, below.
The following functions are usually used in the initialization code:
  • createSlider() function Create a slider that controls a number. Example: t = createSlider("some number", -1, 4, 2) will create a slider ranging over -1 to 4 and starting at 2. Then ex. nextPoint = currentPoint + t in your code gives your end user 'live' control over the scalar value, t. Optionally you can specifiy a 5th parameter which, when false, prevents the existing points from being cleared when the user slides the slider. Example: slider = createSlider("rotation", 0, 6.28, 0, false). You can also pass arguments to noUiSlider.create() by using a Javascript object as the 5th parameter, like {step: 0.01} to set a custom step increment.
  • setTargetsCount(), setPointsCount(), and setOpacity() functions Set the fields seen above, programmatically. Example: setTargetsCount(5) will set the Targets field to 5.
  • getTargetsCount(), getPointsCount(), and getOpacity() functions Get the current values of the fields seen above. Example: n = getTargetsCount().
Advanced example - Avoid previous target:
# 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)

# The rest of this code goes 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
Advanced example - The Corcoran Attractor:
setTargetsCount(6)
setPointsCount(300000)
setOpacity(0.5)

# Create user controls for the parameters
phase_offset = createSlider("Phase offset", 0, 2*math.pi, 5/2, {display: "inline"})
amplitude = createSlider("Amplitude", -3/2, 4, 1/2, {display: "inline"})
bias = createSlider("Bias", -1, 7/4, 1/2, {display: "inline"})

nextTargetIndex = math.randomInt(1, targetPointsLength+1)
nextTarget = targetPoints[nextTargetIndex, :]

displacement_factors = math.matrix([
  [math.sin(nextTarget[1,2] + phase_offset) * amplitude + bias,
   math.sin(nextTarget[1,1] + phase_offset) * amplitude + bias,
   math.sin(nextTarget[1,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)

source   2D version