├── Sketches └── test01 │ ├── data │ └── color-dot.png │ ├── test01.pde │ └── OPC.pde ├── .gitattributes ├── README.md ├── templates └── main.html ├── webLamp.py ├── .gitignore ├── opc.py ├── lampAnimation.py ├── LEDlamp.py └── pnoise.py /Sketches/test01/data/color-dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinpinkney/LEDlamp/master/Sketches/test01/data/color-dot.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LED lamp 2 | ====== 3 | Code for an LED lamp 4 | 5 | Hardware 6 | ------ 7 | Powered by: 8 | 9 | * Raspberry Pi 10 | * Python 11 | * Flask 12 | * Fadecandy 13 | * 60 addressable RGB LEDs (WS2812B) 14 | * Some wire and connectors 15 | * Some aluminium tube 16 | * Something needed to diffuse the LEDs 17 | 18 | Aim 19 | ------ 20 | LED lamp to display colours and change, probably talks 21 | to the internet somehow and controllable over the web. 22 | 23 | Progress 24 | ------ 25 | 26 | *Currently:* 27 | Form input into Flask served page sets colour of LED strip 28 | 29 | *Next:* 30 | ??? 31 | 32 | *Previously:* 33 | * Displaying single colours (with some noise), fades between 34 | other colours at random intervals. 35 | 36 | * One thread responsible for running LED animations (currently displaying a colour with noise, and transitions). Flask webserver runs listening for request for a page which triggers a change to a random colour. -------------------------------------------------------------------------------- /Sketches/test01/test01.pde: -------------------------------------------------------------------------------- 1 | Boolean connected = true; 2 | 3 | OPC opc; 4 | PImage dot; 5 | int w = 10; 6 | int h = 6; 7 | int spacing = 50; 8 | 9 | color c1 = color(255, 20, 100); 10 | color c2 = color(100, 20, 10); 11 | float t = 0; 12 | 13 | void setup() 14 | { 15 | size(spacing*(w+2), spacing*(h+2)); 16 | 17 | 18 | if (connected == true) { 19 | // Connect to the local instance of fcserver 20 | opc = new OPC(this, "127.0.0.1", 7890); 21 | opc.ledGrid(0, 10, 6, width/2, height/2, spacing, spacing, 0, false); 22 | } 23 | } 24 | 25 | void draw() 26 | { 27 | background(0); 28 | c2 = color(255.0*mouseY/height, 200, 255); 29 | 30 | // for (int iLine = 0; iLine 2 | 3 | LED lamp 4 | 5 | 47 | 48 | 49 | 50 | 51 |
52 |

Welcome to the lamp!

53 |

It is currently {{ time }} 54 | 55 |

Set the colour of the lamp (use a hex value) 56 | 57 |

58 |

New colour: 59 | 60 | 61 |

62 |
63 | 64 | -------------------------------------------------------------------------------- /webLamp.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | import datetime 3 | import threading 4 | import time 5 | import Queue 6 | from lampAnimation import * 7 | 8 | 9 | queue = Queue.Queue() 10 | 11 | class ThreadClass(threading.Thread): 12 | 13 | def __init__(self,queue): 14 | threading.Thread.__init__(self) 15 | self.queue = queue 16 | self.counter = 0 17 | 18 | def run(self): 19 | print 'running lamp thread' 20 | # Set up the opc client 21 | self.client = opc.Client('localhost:7890') 22 | 23 | # Set up a colour animation and set an initial colour 24 | self.anim = ColourAnimation(60) 25 | self.anim.setColour([100,50,30],0.0) 26 | self.anim.setDimensions(10,6) 27 | self.anim.setNoise(1, 0.05) 28 | 29 | # Run the animation, and randomly change colour 30 | while True: 31 | 32 | self.anim.update(0.1) 33 | self.pixels = self.anim.render() 34 | self.client.put_pixels(self.pixels) 35 | time.sleep(0.1) 36 | # Check for a new colour in the queue 37 | try: 38 | text = self.queue.get(False) 39 | if len(text) > 0: 40 | print text 41 | self.anim.setColour(text,noise=3.0) 42 | except: 43 | pass 44 | 45 | def create_app(): 46 | app = Flask(__name__) 47 | t = ThreadClass(queue) 48 | t.setDaemon(True) 49 | t.start() 50 | 51 | @app.route("/") 52 | def hello(): 53 | now = datetime.datetime.now() 54 | timeString = now.strftime("%Y-%m-%d %H:%M:%S") 55 | templateData = { 56 | 'time': timeString 57 | } 58 | return render_template('main.html', **templateData) 59 | 60 | @app.route('/change_colour', methods=['POST']) 61 | def handle_data(): 62 | cHex = request.form['newcolour'] 63 | cInt = [int(cHex[0:2],16), 64 | int(cHex[2:4],16), 65 | int(cHex[4:6],16)] 66 | queue.put(cInt) 67 | now = datetime.datetime.now() 68 | timeString = now.strftime("%Y-%m-%d %H:%M:%S") 69 | templateData = { 70 | 'time': timeString 71 | } 72 | return render_template('main.html', **templateData) 73 | 74 | return app 75 | 76 | if __name__ == "__main__": 77 | 78 | app = create_app() 79 | app.run(host='0.0.0.0', port=80, debug=True, use_reloader=False) 80 | 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /opc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Python Client library for Open Pixel Control 4 | http://github.com/zestyping/openpixelcontrol 5 | 6 | Sends pixel values to an Open Pixel Control server to be displayed. 7 | http://openpixelcontrol.org/ 8 | 9 | Recommended use: 10 | 11 | import opc 12 | 13 | # Create a client object 14 | client = opc.Client('localhost:7890') 15 | 16 | # Test if it can connect (optional) 17 | if client.can_connect(): 18 | print('connected to %s' % ADDRESS) 19 | else: 20 | # We could exit here, but instead let's just print a warning 21 | # and then keep trying to send pixels in case the server 22 | # appears later 23 | print('WARNING: could not connect to %s' % ADDRESS) 24 | 25 | # Send pixels forever at 30 frames per second 26 | while True: 27 | my_pixels = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] 28 | if client.put_pixels(my_pixels, channel=0): 29 | print('...') 30 | else: 31 | print('not connected') 32 | time.sleep(1/30.0) 33 | 34 | """ 35 | 36 | import socket 37 | import struct 38 | 39 | class Client(object): 40 | 41 | def __init__(self, server_ip_port, long_connection=True, verbose=False): 42 | """Create an OPC client object which sends pixels to an OPC server. 43 | 44 | server_ip_port should be an ip:port or hostname:port as a single string. 45 | For example: '127.0.0.1:7890' or 'localhost:7890' 46 | 47 | There are two connection modes: 48 | * In long connection mode, we try to maintain a single long-lived 49 | connection to the server. If that connection is lost we will try to 50 | create a new one whenever put_pixels is called. This mode is best 51 | when there's high latency or very high framerates. 52 | * In short connection mode, we open a connection when it's needed and 53 | close it immediately after. This means creating a connection for each 54 | call to put_pixels. Keeping the connection usually closed makes it 55 | possible for others to also connect to the server. 56 | 57 | A connection is not established during __init__. To check if a 58 | connection will succeed, use can_connect(). 59 | 60 | If verbose is True, the client will print debugging info to the console. 61 | 62 | """ 63 | self.verbose = verbose 64 | 65 | self._long_connection = long_connection 66 | 67 | self._ip, self._port = server_ip_port.split(':') 68 | self._port = int(self._port) 69 | 70 | self._socket = None # will be None when we're not connected 71 | 72 | def _debug(self, m): 73 | if self.verbose: 74 | print(' %s' % str(m)) 75 | 76 | def _ensure_connected(self): 77 | """Set up a connection if one doesn't already exist. 78 | 79 | Return True on success or False on failure. 80 | 81 | """ 82 | if self._socket: 83 | self._debug('_ensure_connected: already connected, doing nothing') 84 | return True 85 | 86 | try: 87 | self._debug('_ensure_connected: trying to connect...') 88 | self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 89 | self._socket.connect((self._ip, self._port)) 90 | self._debug('_ensure_connected: ...success') 91 | return True 92 | except socket.error: 93 | self._debug('_ensure_connected: ...failure') 94 | self._socket = None 95 | return False 96 | 97 | def disconnect(self): 98 | """Drop the connection to the server, if there is one.""" 99 | self._debug('disconnecting') 100 | if self._socket: 101 | self._socket.close() 102 | self._socket = None 103 | 104 | def can_connect(self): 105 | """Try to connect to the server. 106 | 107 | Return True on success or False on failure. 108 | 109 | If in long connection mode, this connection will be kept and re-used for 110 | subsequent put_pixels calls. 111 | 112 | """ 113 | success = self._ensure_connected() 114 | if not self._long_connection: 115 | self.disconnect() 116 | return success 117 | 118 | def put_pixels(self, pixels, channel=0): 119 | """Send the list of pixel colors to the OPC server on the given channel. 120 | 121 | channel: Which strand of lights to send the pixel colors to. 122 | Must be an int in the range 0-255 inclusive. 123 | 0 is a special value which means "all channels". 124 | 125 | pixels: A list of 3-tuples representing rgb colors. 126 | Each value in the tuple should be in the range 0-255 inclusive. 127 | For example: [(255, 255, 255), (0, 0, 0), (127, 0, 0)] 128 | Floats will be rounded down to integers. 129 | Values outside the legal range will be clamped. 130 | 131 | Will establish a connection to the server as needed. 132 | 133 | On successful transmission of pixels, return True. 134 | On failure (bad connection), return False. 135 | 136 | The list of pixel colors will be applied to the LED string starting 137 | with the first LED. It's not possible to send a color just to one 138 | LED at a time (unless it's the first one). 139 | 140 | """ 141 | self._debug('put_pixels: connecting') 142 | is_connected = self._ensure_connected() 143 | if not is_connected: 144 | self._debug('put_pixels: not connected. ignoring these pixels.') 145 | return False 146 | 147 | # build OPC message 148 | len_hi_byte = int(len(pixels)*3 / 256) 149 | len_lo_byte = (len(pixels)*3) % 256 150 | header = chr(channel) + chr(0) + chr(len_hi_byte) + chr(len_lo_byte) 151 | pieces = [header] + [ struct.pack( "BBB", 152 | min(255, max(0, int(r))), 153 | min(255, max(0, int(g))), 154 | min(255, max(0, int(b)))) for r, g, b in pixels ] 155 | 156 | message = ''.join(pieces) 157 | 158 | self._debug('put_pixels: sending pixels to server') 159 | try: 160 | self._socket.send(message) 161 | except socket.error: 162 | self._debug('put_pixels: connection lost. could not send pixels.') 163 | self._socket = None 164 | return False 165 | 166 | if not self._long_connection: 167 | self._debug('put_pixels: disconnecting') 168 | self.disconnect() 169 | 170 | return True 171 | 172 | 173 | -------------------------------------------------------------------------------- /lampAnimation.py: -------------------------------------------------------------------------------- 1 | import opc 2 | import time 3 | import random 4 | import math 5 | from pnoise import raw_noise_2d 6 | from pnoise import raw_noise_3d 7 | 8 | class BaseAnimation(): 9 | """ Class to handle animation of a single colour for all LEDs, 10 | and animation between colours 11 | """ 12 | 13 | def __init__(self, nLEDs): 14 | """ Create the list of Pixel objects """ 15 | # Specify the number of leds 16 | self.nLEDs = nLEDs 17 | self.dimensions = False 18 | self.time = 0 19 | 20 | # Each pixel is an object 21 | self.pixels = [] 22 | for LED in range(self.nLEDs): 23 | self.pixels.append(Pixel()) 24 | 25 | def setDimensions(self, w, h): 26 | """ Set dimension for spatially noisy transitions""" 27 | self.dimensions = True 28 | self.w = w 29 | self.h = h 30 | 31 | def update(self, dt): 32 | """ Update all the pixels objects """ 33 | self.time += dt 34 | for pix in self.pixels: 35 | pix.update(dt) 36 | 37 | def render(self): 38 | """ Get and return the colour for each pixel""" 39 | colours = [] 40 | for pix in self.pixels: 41 | colours.append(pix.getColour()) 42 | 43 | return colours 44 | 45 | class ColourAnimation(BaseAnimation): 46 | 47 | def setNoise(self, noiseFreq, noiseAmp): 48 | """ Add Perlin noise to the pixels """ 49 | for pix in self.pixels: 50 | pix.setNoise(noiseFreq,noiseAmp) 51 | 52 | def setColour(self, c, noise=0.0): 53 | """ Change the colour of all the pixels to a target colour 54 | Noise defines the time in seconds over which the transition can occur 55 | """ 56 | print('Setting colours to ' + str(c)) 57 | if self.dimensions: 58 | for idx, pix in enumerate(self.pixels): 59 | pix.setC(c, noise*(0.5 + 0.5*raw_noise_3d( self.time, 0.1*idx%self.w , 0.1*math.floor(idx/self.h) ))) 60 | else: 61 | for pix in self.pixels: 62 | pix.setC(c, noise*random.random()) 63 | 64 | class Pixel(): 65 | """ Class to handle the behaviour of a single pixel""" 66 | 67 | # Static state variables 68 | STATE_STATIC = 0 69 | STATE_CHANGING = 1 70 | 71 | def __init__(self): 72 | """ Initialise the pixel to off """ 73 | self.c = [0,0,0] # Current colour 74 | self.targetC = [0,0,0] # Target colour when interpolating 75 | self.origC = [0,0,0] # Record of original colour when interpolating 76 | self.dt = 0.0 # Interpolation time 77 | self.state = Pixel.STATE_STATIC # Current state of the pixel 78 | self.time = time.time() # Pixel internal clock 79 | self.noiseFreq = 0 # Frequency of Perlin noise 80 | self.noiseOffset = 0 # Offset of Perlin noise 81 | self.noise = False # If we should apply colour noise 82 | 83 | def setC(self, newC, dt=0.0): 84 | """ Method to change the colour of the pixel 85 | to colour newC, over time dt 86 | """ 87 | 88 | if dt>0: 89 | self.origC = self.c[:] # Record the original colour 90 | self.targetC = newC[:] # Set the target 91 | self.timeStart = self.time # Time at which colour change starts 92 | self.timeEnd = self.time + dt # Time at which colour change ends 93 | self.state = Pixel.STATE_CHANGING # Change state 94 | else: 95 | self.c = newC[:] 96 | 97 | def update(self, dt): 98 | """ Update the Pixel""" 99 | 100 | # Increment the internal clock 101 | self.time += dt 102 | 103 | if self.state is Pixel.STATE_CHANGING: 104 | 105 | # Interpolate toward the target colour 106 | timeLeft = float(self.timeEnd - self.time) 107 | totalTime = float(self.timeEnd - self.timeStart) 108 | 109 | # Check how much time is left in the interpolation 110 | if timeLeft > 0 and totalTime != 0: 111 | blendFraction = timeLeft/totalTime 112 | 113 | self.c[0] = int(blendFraction*self.origC[0] + (1 - blendFraction)*self.targetC[0]) 114 | self.c[1] = int(blendFraction*self.origC[1] + (1 - blendFraction)*self.targetC[1]) 115 | self.c[2] = int(blendFraction*self.origC[2] + (1 - blendFraction)*self.targetC[2]) 116 | 117 | else: 118 | self.c = self.targetC[:] 119 | self.state = Pixel.STATE_STATIC 120 | 121 | def getColour(self): 122 | """ Returns the current colour of the pixel """ 123 | 124 | if self.noise: 125 | 126 | # Add Perlin noise to the current Pixel colour 127 | returnC = [0,0,0] 128 | returnC[0] = int(self.c[0]*(1 + self.noiseAmp*raw_noise_2d( 0, self.noiseOffset + self.time*self.noiseFreq))) 129 | returnC[1] = int(self.c[1]*(1 + self.noiseAmp*raw_noise_2d(100, self.noiseOffset + self.time*self.noiseFreq))) 130 | returnC[2] = int(self.c[2]*(1 + self.noiseAmp*raw_noise_2d(200, self.noiseOffset + self.time*self.noiseFreq))) 131 | 132 | # cap the values 133 | # TODO make this a utility function 134 | returnC[0] = max( min(returnC[0],255), 0 ) 135 | returnC[1] = max( min(returnC[1],255), 0 ) 136 | returnC[2] = max( min(returnC[2],255), 0 ) 137 | 138 | else: 139 | returnC = self.c[:] 140 | 141 | return returnC 142 | 143 | def setNoise(self,noiseFreq, noiseAmp): 144 | """ Enables noise on the Pixel colour """ 145 | 146 | self.noiseFreq = noiseFreq 147 | # Ensure that different pixels are at different points on the noise profile 148 | self.noiseOffset = 1000*random.random() 149 | self.noiseAmp = noiseAmp 150 | self.noise = True -------------------------------------------------------------------------------- /LEDlamp.py: -------------------------------------------------------------------------------- 1 | import opc 2 | import time 3 | import random 4 | import math 5 | from pnoise import raw_noise_2d 6 | from pnoise import raw_noise_3d 7 | 8 | class BaseAnimation(): 9 | """ Class to handle animation of a single colour for all LEDs, 10 | and animation between colours 11 | """ 12 | 13 | def __init__(self, nLEDs): 14 | """ Create the list of Pixel objects """ 15 | # Specify the number of leds 16 | self.nLEDs = nLEDs 17 | self.dimensions = False 18 | self.time = 0 19 | 20 | # Each pixel is an object 21 | self.pixels = [] 22 | for LED in range(self.nLEDs): 23 | self.pixels.append(Pixel()) 24 | 25 | def setDimensions(self, w, h): 26 | """ Set dimension for spatially noisy transitions""" 27 | self.dimensions = True 28 | self.w = w 29 | self.h = h 30 | 31 | def update(self, dt): 32 | """ Update all the pixels objects """ 33 | self.time += dt 34 | for pix in self.pixels: 35 | pix.update(dt) 36 | 37 | def render(self): 38 | """ Get and return the colour for each pixel""" 39 | colours = [] 40 | for pix in self.pixels: 41 | colours.append(pix.getColour()) 42 | 43 | return colours 44 | 45 | class ColourAnimation(BaseAnimation): 46 | 47 | def setNoise(self, noiseFreq, noiseAmp): 48 | """ Add Perlin noise to the pixels """ 49 | for pix in self.pixels: 50 | pix.setNoise(noiseFreq,noiseAmp) 51 | 52 | def setColour(self, c, noise=0.0): 53 | """ Change the colour of all the pixels to a target colour 54 | Noise defines the time in seconds over which the transition can occur 55 | """ 56 | print('Setting colours to ' + str(c)) 57 | if self.dimensions: 58 | for idx, pix in enumerate(self.pixels): 59 | pix.setC(c, noise*(0.5 + 0.5*raw_noise_3d( self.time, 0.1*idx%self.w , 0.1*math.floor(idx/self.h) ))) 60 | else: 61 | for pix in self.pixels: 62 | pix.setC(c, noise*random.random()) 63 | 64 | class Pixel(): 65 | """ Class to handle the behaviour of a single pixel""" 66 | 67 | # Static state variables 68 | STATE_STATIC = 0 69 | STATE_CHANGING = 1 70 | 71 | def __init__(self): 72 | """ Initialise the pixel to off """ 73 | self.c = [0,0,0] # Current colour 74 | self.targetC = [0,0,0] # Target colour when interpolating 75 | self.origC = [0,0,0] # Record of original colour when interpolating 76 | self.dt = 0.0 # Interpolation time 77 | self.state = Pixel.STATE_STATIC # Current state of the pixel 78 | self.time = time.time() # Pixel internal clock 79 | self.noiseFreq = 0 # Frequency of Perlin noise 80 | self.noiseOffset = 0 # Offset of Perlin noise 81 | self.noise = False # If we should apply colour noise 82 | 83 | def setC(self, newC, dt=0.0): 84 | """ Method to change the colour of the pixel 85 | to colour newC, over time dt 86 | """ 87 | 88 | if dt>0: 89 | self.origC = self.c[:] # Record the original colour 90 | self.targetC = newC[:] # Set the target 91 | self.timeStart = self.time # Time at which colour change starts 92 | self.timeEnd = self.time + dt # Time at which colour change ends 93 | self.state = Pixel.STATE_CHANGING # Change state 94 | else: 95 | self.c = newC[:] 96 | 97 | def update(self, dt): 98 | """ Update the Pixel""" 99 | 100 | # Increment the internal clock 101 | self.time += dt 102 | 103 | if self.state is Pixel.STATE_CHANGING: 104 | 105 | # Interpolate toward the target colour 106 | timeLeft = float(self.timeEnd - self.time) 107 | totalTime = float(self.timeEnd - self.timeStart) 108 | 109 | # Check how much time is left in the interpolation 110 | if timeLeft > 0 and totalTime != 0: 111 | blendFraction = timeLeft/totalTime 112 | 113 | self.c[0] = int(blendFraction*self.origC[0] + (1 - blendFraction)*self.targetC[0]) 114 | self.c[1] = int(blendFraction*self.origC[1] + (1 - blendFraction)*self.targetC[1]) 115 | self.c[2] = int(blendFraction*self.origC[2] + (1 - blendFraction)*self.targetC[2]) 116 | 117 | else: 118 | self.c = self.targetC[:] 119 | self.state = Pixel.STATE_STATIC 120 | 121 | def getColour(self): 122 | """ Returns the current colour of the pixel """ 123 | 124 | if self.noise: 125 | 126 | # Add Perlin noise to the current Pixel colour 127 | returnC = [0,0,0] 128 | returnC[0] = int(self.c[0]*(1 + self.noiseAmp*raw_noise_2d( 0, self.noiseOffset + self.time*self.noiseFreq))) 129 | returnC[1] = int(self.c[1]*(1 + self.noiseAmp*raw_noise_2d(100, self.noiseOffset + self.time*self.noiseFreq))) 130 | returnC[2] = int(self.c[2]*(1 + self.noiseAmp*raw_noise_2d(200, self.noiseOffset + self.time*self.noiseFreq))) 131 | 132 | # cap the values 133 | # TODO make this a utility function 134 | returnC[0] = max( min(returnC[0],255), 0 ) 135 | returnC[1] = max( min(returnC[1],255), 0 ) 136 | returnC[2] = max( min(returnC[2],255), 0 ) 137 | 138 | else: 139 | returnC = self.c[:] 140 | 141 | return returnC 142 | 143 | def setNoise(self,noiseFreq, noiseAmp): 144 | """ Enables noise on the Pixel colour """ 145 | 146 | self.noiseFreq = noiseFreq 147 | # Ensure that different pixels are at different points on the noise profile 148 | self.noiseOffset = 1000*random.random() 149 | self.noiseAmp = noiseAmp 150 | self.noise = True 151 | 152 | if __name__ == '__main__': 153 | 154 | # Set up the opc client 155 | client = opc.Client('localhost:7890') 156 | 157 | # Set up a colour animation and set an initial colour 158 | anim = ColourAnimation(60) 159 | anim.setColour([100,50,30],0.0) 160 | anim.setDimensions(10,6) 161 | anim.setNoise(1, 0.05) 162 | 163 | # Run the animation, and randomly change colour 164 | while True: 165 | 166 | anim.update(0.1) 167 | pixels = anim.render() 168 | client.put_pixels(pixels) 169 | time.sleep(0.1) 170 | 171 | if random.random() < 0.03: 172 | # Change the colours of the pixels (over the course of 1s) 173 | anim.setColour([random.randint(0,255), 174 | random.randint(0,255), 175 | random.randint(0,255)],noise=3.0) 176 | -------------------------------------------------------------------------------- /Sketches/test01/OPC.pde: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple Open Pixel Control client for Processing, 3 | * designed to sample each LED's color from some point on the canvas. 4 | * 5 | * Micah Elizabeth Scott, 2013 6 | * This file is released into the public domain. 7 | */ 8 | 9 | import java.net.*; 10 | import java.util.Arrays; 11 | 12 | public class OPC 13 | { 14 | Socket socket; 15 | OutputStream output; 16 | String host; 17 | int port; 18 | 19 | int[] pixelLocations; 20 | byte[] packetData; 21 | byte firmwareConfig; 22 | String colorCorrection; 23 | boolean enableShowLocations; 24 | 25 | OPC(PApplet parent, String host, int port) 26 | { 27 | this.host = host; 28 | this.port = port; 29 | this.enableShowLocations = true; 30 | parent.registerDraw(this); 31 | } 32 | 33 | // Set the location of a single LED 34 | void led(int index, int x, int y) 35 | { 36 | // For convenience, automatically grow the pixelLocations array. We do want this to be an array, 37 | // instead of a HashMap, to keep draw() as fast as it can be. 38 | if (pixelLocations == null) { 39 | pixelLocations = new int[index + 1]; 40 | } else if (index >= pixelLocations.length) { 41 | pixelLocations = Arrays.copyOf(pixelLocations, index + 1); 42 | } 43 | 44 | pixelLocations[index] = x + width * y; 45 | } 46 | 47 | // Set the location of several LEDs arranged in a strip. 48 | // Angle is in radians, measured clockwise from +X. 49 | // (x,y) is the center of the strip. 50 | void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed) 51 | { 52 | float s = sin(angle); 53 | float c = cos(angle); 54 | for (int i = 0; i < count; i++) { 55 | led(reversed ? (index + count - 1 - i) : (index + i), 56 | (int)(x + (i - (count-1)/2.0) * spacing * c + 0.5), 57 | (int)(y + (i - (count-1)/2.0) * spacing * s + 0.5)); 58 | } 59 | } 60 | 61 | // Set the locations of a ring of LEDs. The center of the ring is at (x, y), 62 | // with "radius" pixels between the center and each LED. The first LED is at 63 | // the indicated angle, in radians, measured clockwise from +X. 64 | void ledRing(int index, int count, float x, float y, float radius, float angle) 65 | { 66 | for (int i = 0; i < count; i++) { 67 | float a = angle + i * 2 * PI / count; 68 | led(index + i, (int)(x - radius * cos(a) + 0.5), 69 | (int)(y - radius * sin(a) + 0.5)); 70 | } 71 | } 72 | 73 | // Set the location of several LEDs arranged in a grid. The first strip is 74 | // at 'angle', measured in radians clockwise from +X. 75 | // (x,y) is the center of the grid. 76 | void ledGrid(int index, int stripLength, int numStrips, float x, float y, 77 | float ledSpacing, float stripSpacing, float angle, boolean zigzag) 78 | { 79 | float s = sin(angle + HALF_PI); 80 | float c = cos(angle + HALF_PI); 81 | for (int i = 0; i < numStrips; i++) { 82 | ledStrip(index + stripLength * i, stripLength, 83 | x + (i - (numStrips-1)/2.0) * stripSpacing * c, 84 | y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing, 85 | angle, zigzag && (i % 2) == 1); 86 | } 87 | } 88 | 89 | // Set the location of 64 LEDs arranged in a uniform 8x8 grid. 90 | // (x,y) is the center of the grid. 91 | void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag) 92 | { 93 | ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag); 94 | } 95 | 96 | // Should the pixel sampling locations be visible? This helps with debugging. 97 | // Showing locations is enabled by default. You might need to disable it if our drawing 98 | // is interfering with your processing sketch, or if you'd simply like the screen to be 99 | // less cluttered. 100 | void showLocations(boolean enabled) 101 | { 102 | enableShowLocations = enabled; 103 | } 104 | 105 | // Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color 106 | // resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second. 107 | // Dithering is on by default. 108 | void setDithering(boolean enabled) 109 | { 110 | if (enabled) 111 | firmwareConfig &= ~0x01; 112 | else 113 | firmwareConfig |= 0x01; 114 | sendFirmwareConfigPacket(); 115 | } 116 | 117 | // Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames 118 | // in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make 119 | // fades very smooth. Interpolation is on by default. 120 | void setInterpolation(boolean enabled) 121 | { 122 | if (enabled) 123 | firmwareConfig &= ~0x02; 124 | else 125 | firmwareConfig |= 0x02; 126 | sendFirmwareConfigPacket(); 127 | } 128 | 129 | // Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet. 130 | // This is the default configuration for the LED. 131 | void statusLedAuto() 132 | { 133 | firmwareConfig &= 0x0C; 134 | sendFirmwareConfigPacket(); 135 | } 136 | 137 | // Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control. 138 | void setStatusLed(boolean on) 139 | { 140 | firmwareConfig |= 0x04; // Manual LED control 141 | if (on) 142 | firmwareConfig |= 0x08; 143 | else 144 | firmwareConfig &= ~0x08; 145 | sendFirmwareConfigPacket(); 146 | } 147 | 148 | // Set the color correction parameters 149 | void setColorCorrection(float gamma, float red, float green, float blue) 150 | { 151 | colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}"; 152 | sendColorCorrectionPacket(); 153 | } 154 | 155 | // Set custom color correction parameters from a string 156 | void setColorCorrection(String s) 157 | { 158 | colorCorrection = s; 159 | sendColorCorrectionPacket(); 160 | } 161 | 162 | // Send a packet with the current firmware configuration settings 163 | void sendFirmwareConfigPacket() 164 | { 165 | if (output == null) { 166 | // We'll do this when we reconnect 167 | return; 168 | } 169 | 170 | byte[] packet = new byte[9]; 171 | packet[0] = 0; // Channel (reserved) 172 | packet[1] = (byte)0xFF; // Command (System Exclusive) 173 | packet[2] = 0; // Length high byte 174 | packet[3] = 5; // Length low byte 175 | packet[4] = 0x00; // System ID high byte 176 | packet[5] = 0x01; // System ID low byte 177 | packet[6] = 0x00; // Command ID high byte 178 | packet[7] = 0x02; // Command ID low byte 179 | packet[8] = firmwareConfig; 180 | 181 | try { 182 | output.write(packet); 183 | } catch (Exception e) { 184 | dispose(); 185 | } 186 | } 187 | 188 | // Send a packet with the current color correction settings 189 | void sendColorCorrectionPacket() 190 | { 191 | if (colorCorrection == null) { 192 | // No color correction defined 193 | return; 194 | } 195 | if (output == null) { 196 | // We'll do this when we reconnect 197 | return; 198 | } 199 | 200 | byte[] content = colorCorrection.getBytes(); 201 | int packetLen = content.length + 4; 202 | byte[] header = new byte[8]; 203 | header[0] = 0; // Channel (reserved) 204 | header[1] = (byte)0xFF; // Command (System Exclusive) 205 | header[2] = (byte)(packetLen >> 8); 206 | header[3] = (byte)(packetLen & 0xFF); 207 | header[4] = 0x00; // System ID high byte 208 | header[5] = 0x01; // System ID low byte 209 | header[6] = 0x00; // Command ID high byte 210 | header[7] = 0x01; // Command ID low byte 211 | 212 | try { 213 | output.write(header); 214 | output.write(content); 215 | } catch (Exception e) { 216 | dispose(); 217 | } 218 | } 219 | 220 | // Automatically called at the end of each draw(). 221 | // This handles the automatic Pixel to LED mapping. 222 | // If you aren't using that mapping, this function has no effect. 223 | // In that case, you can call setPixelCount(), setPixel(), and writePixels() 224 | // separately. 225 | void draw() 226 | { 227 | if (pixelLocations == null) { 228 | // No pixels defined yet 229 | return; 230 | } 231 | 232 | if (output == null) { 233 | // Try to (re)connect 234 | connect(); 235 | } 236 | if (output == null) { 237 | return; 238 | } 239 | 240 | int numPixels = pixelLocations.length; 241 | int ledAddress = 4; 242 | 243 | setPixelCount(numPixels); 244 | loadPixels(); 245 | 246 | for (int i = 0; i < numPixels; i++) { 247 | int pixelLocation = pixelLocations[i]; 248 | int pixel = pixels[pixelLocation]; 249 | 250 | packetData[ledAddress] = (byte)(pixel >> 16); 251 | packetData[ledAddress + 1] = (byte)(pixel >> 8); 252 | packetData[ledAddress + 2] = (byte)pixel; 253 | ledAddress += 3; 254 | 255 | if (enableShowLocations) { 256 | pixels[pixelLocation] = 0xFFFFFF ^ pixel; 257 | } 258 | } 259 | 260 | writePixels(); 261 | 262 | if (enableShowLocations) { 263 | updatePixels(); 264 | } 265 | } 266 | 267 | // Change the number of pixels in our output packet. 268 | // This is normally not needed; the output packet is automatically sized 269 | // by draw() and by setPixel(). 270 | void setPixelCount(int numPixels) 271 | { 272 | int numBytes = 3 * numPixels; 273 | int packetLen = 4 + numBytes; 274 | if (packetData == null || packetData.length != packetLen) { 275 | // Set up our packet buffer 276 | packetData = new byte[packetLen]; 277 | packetData[0] = 0; // Channel 278 | packetData[1] = 0; // Command (Set pixel colors) 279 | packetData[2] = (byte)(numBytes >> 8); 280 | packetData[3] = (byte)(numBytes & 0xFF); 281 | } 282 | } 283 | 284 | // Directly manipulate a pixel in the output buffer. This isn't needed 285 | // for pixels that are mapped to the screen. 286 | void setPixel(int number, color c) 287 | { 288 | int offset = 4 + number * 3; 289 | if (packetData == null || packetData.length < offset + 3) { 290 | setPixelCount(number + 1); 291 | } 292 | 293 | packetData[offset] = (byte) (c >> 16); 294 | packetData[offset + 1] = (byte) (c >> 8); 295 | packetData[offset + 2] = (byte) c; 296 | } 297 | 298 | // Read a pixel from the output buffer. If the pixel was mapped to the display, 299 | // this returns the value we captured on the previous frame. 300 | color getPixel(int number) 301 | { 302 | int offset = 4 + number * 3; 303 | if (packetData == null || packetData.length < offset + 3) { 304 | return 0; 305 | } 306 | return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2]; 307 | } 308 | 309 | // Transmit our current buffer of pixel values to the OPC server. This is handled 310 | // automatically in draw() if any pixels are mapped to the screen, but if you haven't 311 | // mapped any pixels to the screen you'll want to call this directly. 312 | void writePixels() 313 | { 314 | if (packetData == null || packetData.length == 0) { 315 | // No pixel buffer 316 | return; 317 | } 318 | if (output == null) { 319 | // Try to (re)connect 320 | connect(); 321 | } 322 | if (output == null) { 323 | return; 324 | } 325 | 326 | try { 327 | output.write(packetData); 328 | } catch (Exception e) { 329 | dispose(); 330 | } 331 | } 332 | 333 | void dispose() 334 | { 335 | // Destroy the socket. Called internally when we've disconnected. 336 | if (output != null) { 337 | println("Disconnected from OPC server"); 338 | } 339 | socket = null; 340 | output = null; 341 | } 342 | 343 | void connect() 344 | { 345 | // Try to connect to the OPC server. This normally happens automatically in draw() 346 | try { 347 | socket = new Socket(host, port); 348 | socket.setTcpNoDelay(true); 349 | output = socket.getOutputStream(); 350 | println("Connected to OPC server"); 351 | } catch (ConnectException e) { 352 | dispose(); 353 | } catch (IOException e) { 354 | dispose(); 355 | } 356 | 357 | sendColorCorrectionPacket(); 358 | sendFirmwareConfigPacket(); 359 | } 360 | } 361 | 362 | -------------------------------------------------------------------------------- /pnoise.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Eliot Eshelman 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | ############################################################################### 16 | 17 | """2D, 3D and 4D Simplex Noise functions return 'random' values in (-1, 1). 18 | 19 | This algorithm was originally designed by Ken Perlin, but my code has been 20 | adapted from the implementation written by Stefan Gustavson (stegu@itn.liu.se) 21 | 22 | Raw Simplex noise functions return the value generated by Ken's algorithm. 23 | 24 | Scaled Raw Simplex noise functions adjust the range of values returned from the 25 | traditional (-1, 1) to whichever bounds are passed to the function. 26 | 27 | Multi-Octave Simplex noise functions compine multiple noise values to create a 28 | more complex result. Each successive layer of noise is adjusted and scaled. 29 | 30 | Scaled Multi-Octave Simplex noise functions scale the values returned from the 31 | traditional (-1,1) range to whichever range is passed to the function. 32 | 33 | In many cases, you may think you only need a 1D noise function, but in practice 34 | 2D is almost always better. For instance, if you're using the current frame 35 | number as the parameter for the noise, all objects will end up with the same 36 | noise value at each frame. By adding a second parameter on the second 37 | dimension, you can ensure that each gets a unique noise value and they don't 38 | all look identical. 39 | """ 40 | 41 | import math 42 | 43 | def octave_noise_2d(octaves, persistence, scale, x, y): 44 | """2D Multi-Octave Simplex noise. 45 | 46 | For each octave, a higher frequency/lower amplitude function will be added 47 | to the original. The higher the persistence [0-1], the more of each 48 | succeeding octave will be added. 49 | """ 50 | total = 0.0 51 | frequency = scale 52 | amplitude = 1.0 53 | 54 | # We have to keep track of the largest possible amplitude, 55 | # because each octave adds more, and we need a value in [-1, 1]. 56 | maxAmplitude = 0.0; 57 | 58 | for i in range(octaves): 59 | total += raw_noise_2d(x * frequency, y * frequency) * amplitude 60 | frequency *= 2.0 61 | maxAmplitude += amplitude; 62 | amplitude *= persistence 63 | 64 | return total / maxAmplitude 65 | 66 | def octave_noise_3d(octaves, persistence, scale, x, y, z): 67 | """3D Multi-Octave Simplex noise. 68 | 69 | For each octave, a higher frequency/lower amplitude function will be added 70 | to the original. The higher the persistence [0-1], the more of each 71 | succeeding octave will be added. 72 | """ 73 | total = 0.0 74 | frequency = scale 75 | amplitude = 1.0 76 | 77 | # We have to keep track of the largest possible amplitude, 78 | # because each octave adds more, and we need a value in [-1, 1]. 79 | maxAmplitude = 0.0; 80 | 81 | for i in range(octaves): 82 | total += raw_noise_3d( x * frequency, 83 | y * frequency, 84 | z * frequency) * amplitude 85 | frequency *= 2.0 86 | maxAmplitude += amplitude; 87 | amplitude *= persistence 88 | 89 | return total / maxAmplitude 90 | 91 | def octave_noise_4d(octaves, persistence, scale, x, y, z, w): 92 | """4D Multi-Octave Simplex noise. 93 | 94 | For each octave, a higher frequency/lower amplitude function will be added 95 | to the original. The higher the persistence [0-1], the more of each 96 | succeeding octave will be added. 97 | """ 98 | total = 0.0 99 | frequency = scale 100 | amplitude = 1.0 101 | 102 | # We have to keep track of the largest possible amplitude, 103 | # because each octave adds more, and we need a value in [-1, 1]. 104 | maxAmplitude = 0.0; 105 | 106 | for i in range(octaves): 107 | total += raw_noise_4d( x * frequency, 108 | y * frequency, 109 | z * frequency, 110 | w * frequency) * amplitude 111 | frequency *= 2.0 112 | maxAmplitude += amplitude; 113 | amplitude *= persistence 114 | 115 | return total / maxAmplitude 116 | 117 | def scaled_octave_noise_2d(octaves, persistence, scale, loBound, hiBound, x, y): 118 | """2D Scaled Multi-Octave Simplex noise. 119 | 120 | Returned value will be between loBound and hiBound. 121 | """ 122 | return (octave_noise_2d(octaves, persistence, scale, x, y) * 123 | (hiBound - loBound) / 2 + 124 | (hiBound + loBound) / 2) 125 | 126 | def scaled_octave_noise_3d(octaves, persistence, scale, loBound, hiBound, x, y, z): 127 | """3D Scaled Multi-Octave Simplex noise. 128 | 129 | Returned value will be between loBound and hiBound. 130 | """ 131 | return (octave_noise_3d(octaves, persistence, scale, x, y, z) * 132 | (hiBound - loBound) / 2 + 133 | (hiBound + loBound) / 2) 134 | 135 | def scaled_octave_noise_4d(octaves, persistence, scale, loBound, hiBound, x, y, z, w): 136 | """4D Scaled Multi-Octave Simplex noise. 137 | 138 | Returned value will be between loBound and hiBound. 139 | """ 140 | return (octave_noise_4d(octaves, persistence, scale, x, y, z, w) * 141 | (hiBound - loBound) / 2 + 142 | (hiBound + loBound) / 2) 143 | 144 | def scaled_raw_noise_2d(loBound, hiBound, x, y): 145 | """2D Scaled Raw Simplex noise. 146 | 147 | Returned value will be between loBound and hiBound. 148 | """ 149 | return (raw_noise_2d(x, y) * 150 | (hiBound - loBound) / 2+ 151 | (hiBound + loBound) / 2) 152 | 153 | def scaled_raw_noise_3d(loBound, hiBound, x, y, z): 154 | """3D Scaled Raw Simplex noise. 155 | 156 | Returned value will be between loBound and hiBound. 157 | """ 158 | return (raw_noise_3d(x, y, z) * 159 | (hiBound - loBound) / 2+ 160 | (hiBound + loBound) / 2) 161 | 162 | def scaled_raw_noise_4d(loBound, hiBound, x, y, z, w): 163 | """4D Scaled Raw Simplex noise. 164 | 165 | Returned value will be between loBound and hiBound. 166 | """ 167 | return (raw_noise_4d(x, y, z, w) * 168 | (hiBound - loBound) / 2+ 169 | (hiBound + loBound) / 2) 170 | 171 | def raw_noise_2d(x, y): 172 | """2D Raw Simplex noise.""" 173 | # Noise contributions from the three corners 174 | n0, n1, n2 = 0.0, 0.0, 0.0 175 | 176 | # Skew the input space to determine which simplex cell we're in 177 | F2 = 0.5 * (math.sqrt(3.0) - 1.0) 178 | # Hairy skew factor for 2D 179 | s = (x + y) * F2 180 | i = int(x + s) 181 | j = int(y + s) 182 | 183 | G2 = (3.0 - math.sqrt(3.0)) / 6.0 184 | t = float(i + j) * G2 185 | # Unskew the cell origin back to (x,y) space 186 | X0 = i - t 187 | Y0 = j - t 188 | # The x,y distances from the cell origin 189 | x0 = x - X0 190 | y0 = y - Y0 191 | 192 | # For the 2D case, the simplex shape is an equilateral triangle. 193 | # Determine which simplex we are in. 194 | i1, j1 = 0, 0 # Offsets for second (middle) corner of simplex in (i,j) coords 195 | if x0 > y0: # lower triangle, XY order: (0,0)->(1,0)->(1,1) 196 | i1 = 1 197 | j1 = 0 198 | else: # upper triangle, YX order: (0,0)->(0,1)->(1,1) 199 | i1 = 0 200 | j1 = 1 201 | 202 | # A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 203 | # a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 204 | # c = (3-sqrt(3))/6 205 | x1 = x0 - i1 + G2 # Offsets for middle corner in (x,y) unskewed coords 206 | y1 = y0 - j1 + G2 207 | x2 = x0 - 1.0 + 2.0 * G2 # Offsets for last corner in (x,y) unskewed coords 208 | y2 = y0 - 1.0 + 2.0 * G2 209 | 210 | # Work out the hashed gradient indices of the three simplex corners 211 | ii = int(i) & 255 212 | jj = int(j) & 255 213 | gi0 = _perm[ii+_perm[jj]] % 12 214 | gi1 = _perm[ii+i1+_perm[jj+j1]] % 12 215 | gi2 = _perm[ii+1+_perm[jj+1]] % 12 216 | 217 | # Calculate the contribution from the three corners 218 | t0 = 0.5 - x0*x0 - y0*y0 219 | if t0 < 0: 220 | n0 = 0.0 221 | else: 222 | t0 *= t0 223 | n0 = t0 * t0 * dot2d(_grad3[gi0], x0, y0) 224 | 225 | t1 = 0.5 - x1*x1 - y1*y1 226 | if t1 < 0: 227 | n1 = 0.0 228 | else: 229 | t1 *= t1 230 | n1 = t1 * t1 * dot2d(_grad3[gi1], x1, y1) 231 | 232 | t2 = 0.5 - x2*x2-y2*y2 233 | if t2 < 0: 234 | n2 = 0.0 235 | else: 236 | t2 *= t2 237 | n2 = t2 * t2 * dot2d(_grad3[gi2], x2, y2) 238 | 239 | # Add contributions from each corner to get the final noise value. 240 | # The result is scaled to return values in the interval [-1,1]. 241 | return 70.0 * (n0 + n1 + n2) 242 | 243 | def raw_noise_3d(x, y, z): 244 | """3D Raw Simplex noise.""" 245 | # Noise contributions from the four corners 246 | n0, n1, n2, n3 = 0.0, 0.0, 0.0, 0.0 247 | 248 | # Skew the input space to determine which simplex cell we're in 249 | F3 = 1.0/3.0 250 | # Very nice and simple skew factor for 3D 251 | s = (x+y+z) * F3 252 | i = int(x + s) 253 | j = int(y + s) 254 | k = int(z + s) 255 | 256 | G3 = 1.0 / 6.0 257 | t = float(i+j+k) * G3 258 | # Unskew the cell origin back to (x,y,z) space 259 | X0 = i - t 260 | Y0 = j - t 261 | Z0 = k - t 262 | # The x,y,z distances from the cell origin 263 | x0 = x - X0 264 | y0 = y - Y0 265 | z0 = z - Z0 266 | 267 | # For the 3D case, the simplex shape is a slightly irregular tetrahedron. 268 | # Determine which simplex we are in. 269 | i1, j1, k1 = 0,0,0 # Offsets for second corner of simplex in (i,j,k) coords 270 | i2, j2, k2 = 0,0,0 # Offsets for third corner of simplex in (i,j,k) coords 271 | 272 | if x0 >= y0: 273 | if y0 >= z0: # X Y Z order 274 | i1 = 1 275 | j1 = 0 276 | k1 = 0 277 | i2 = 1 278 | j2 = 1 279 | k2 = 0 280 | elif x0 >= z0: # X Z Y order 281 | i1 = 1 282 | j1 = 0 283 | k1 = 0 284 | i2 = 1 285 | j2 = 0 286 | k2 = 1 287 | else: # Z X Y order 288 | i1 = 0 289 | j1 = 0 290 | k1 = 1 291 | i2 = 1 292 | j2 = 0 293 | k2 = 1 294 | else: 295 | if y0 < z0: # Z Y X order 296 | i1 = 0 297 | j1 = 0 298 | k1 = 1 299 | i2 = 0 300 | j2 = 1 301 | k2 = 1 302 | elif x0 < z0: # Y Z X order 303 | i1 = 0 304 | j1 = 1 305 | k1 = 0 306 | i2 = 0 307 | j2 = 1 308 | k2 = 1 309 | else: # Y X Z order 310 | i1 = 0 311 | j1 = 1 312 | k1 = 0 313 | i2 = 1 314 | j2 = 1 315 | k2 = 0 316 | 317 | # A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), 318 | # a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and 319 | # a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where 320 | # c = 1/6. 321 | x1 = x0 - i1 + G3 # Offsets for second corner in (x,y,z) coords 322 | y1 = y0 - j1 + G3 323 | z1 = z0 - k1 + G3 324 | x2 = x0 - i2 + 2.0*G3 # Offsets for third corner in (x,y,z) coords 325 | y2 = y0 - j2 + 2.0*G3 326 | z2 = z0 - k2 + 2.0*G3 327 | x3 = x0 - 1.0 + 3.0*G3 # Offsets for last corner in (x,y,z) coords 328 | y3 = y0 - 1.0 + 3.0*G3 329 | z3 = z0 - 1.0 + 3.0*G3 330 | 331 | # Work out the hashed gradient indices of the four simplex corners 332 | ii = int(i) & 255 333 | jj = int(j) & 255 334 | kk = int(k) & 255 335 | gi0 = _perm[ii+_perm[jj+_perm[kk]]] % 12 336 | gi1 = _perm[ii+i1+_perm[jj+j1+_perm[kk+k1]]] % 12 337 | gi2 = _perm[ii+i2+_perm[jj+j2+_perm[kk+k2]]] % 12 338 | gi3 = _perm[ii+1+_perm[jj+1+_perm[kk+1]]] % 12 339 | 340 | # Calculate the contribution from the four corners 341 | t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 342 | if t0 < 0: 343 | n0 = 0.0 344 | else: 345 | t0 *= t0 346 | n0 = t0 * t0 * dot3d(_grad3[gi0], x0, y0, z0) 347 | 348 | t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 349 | if t1 < 0: 350 | n1 = 0.0 351 | else: 352 | t1 *= t1 353 | n1 = t1 * t1 * dot3d(_grad3[gi1], x1, y1, z1) 354 | 355 | t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 356 | if t2 < 0: 357 | n2 = 0.0 358 | else: 359 | t2 *= t2 360 | n2 = t2 * t2 * dot3d(_grad3[gi2], x2, y2, z2) 361 | 362 | t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 363 | if t3 < 0: 364 | n3 = 0.0 365 | else: 366 | t3 *= t3 367 | n3 = t3 * t3 * dot3d(_grad3[gi3], x3, y3, z3) 368 | 369 | # Add contributions from each corner to get the final noise value. 370 | # The result is scaled to stay just inside [-1,1] 371 | return 32.0 * (n0 + n1 + n2 + n3) 372 | 373 | def raw_noise_4d(x, y, z, w): 374 | """4D Raw Simplex noise.""" 375 | # Noise contributions from the five corners 376 | n0, n1, n2, n3, n4 = 0.0, 0.0, 0.0, 0.0, 0.0 377 | 378 | # The skewing and unskewing factors are hairy again for the 4D case 379 | F4 = (math.sqrt(5.0)-1.0) / 4.0 380 | # Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in 381 | s = (x + y + z + w) * F4 382 | i = int(x + s) 383 | j = int(y + s) 384 | k = int(z + s) 385 | l = int(w + s) 386 | 387 | G4 = (5.0-math.sqrt(5.0)) / 20.0 388 | t = (i + j + k + l) * G4 389 | # Unskew the cell origin back to (x,y,z,w) space 390 | X0 = i - t 391 | Y0 = j - t 392 | Z0 = k - t 393 | W0 = l - t 394 | # The x,y,z,w distances from the cell origin 395 | x0 = x - X0 396 | y0 = y - Y0 397 | z0 = z - Z0 398 | w0 = w - W0 399 | 400 | # For the 4D case, the simplex is a 4D shape I won't even try to describe. 401 | # To find out which of the 24 possible simplices we're in, we need to 402 | # determine the magnitude ordering of x0, y0, z0 and w0. 403 | # The method below is a good way of finding the ordering of x,y,z,w and 404 | # then find the correct traversal order for the simplex we're in. 405 | # First, six pair-wise comparisons are performed between each possible pair 406 | # of the four coordinates, and the results are used to add up binary bits 407 | # for an integer index. 408 | c1 = 32 if x0 > y0 else 0 409 | c2 = 16 if x0 > z0 else 0 410 | c3 = 8 if y0 > z0 else 0 411 | c4 = 4 if x0 > w0 else 0 412 | c5 = 2 if y0 > w0 else 0 413 | c6 = 1 if z0 > w0 else 0 414 | c = c1 + c2 + c3 + c4 + c5 + c6 415 | 416 | i1, j1, k1, l1 = 0,0,0,0 # The integer offsets for the second simplex corner 417 | i2, j2, k2, l2 = 0,0,0,0 # The integer offsets for the third simplex corner 418 | i3, j3, k3, l3 = 0,0,0,0 # The integer offsets for the fourth simplex corner 419 | 420 | # simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. 421 | # Many values of c will never occur, since e.g. x>y>z>w makes x= 3 else 0 426 | j1 = 1 if _simplex[c][1] >= 3 else 0 427 | k1 = 1 if _simplex[c][2] >= 3 else 0 428 | l1 = 1 if _simplex[c][3] >= 3 else 0 429 | # The number 2 in the "simplex" array is at the second largest coordinate. 430 | i2 = 1 if _simplex[c][0] >= 2 else 0 431 | j2 = 1 if _simplex[c][1] >= 2 else 0 432 | k2 = 1 if _simplex[c][2] >= 2 else 0 433 | l2 = 1 if _simplex[c][3] >= 2 else 0 434 | # The number 1 in the "simplex" array is at the second smallest coordinate. 435 | i3 = 1 if _simplex[c][0] >= 1 else 0 436 | j3 = 1 if _simplex[c][1] >= 1 else 0 437 | k3 = 1 if _simplex[c][2] >= 1 else 0 438 | l3 = 1 if _simplex[c][3] >= 1 else 0 439 | # The fifth corner has all coordinate offsets = 1, so no need to look that up. 440 | x1 = x0 - i1 + G4 # Offsets for second corner in (x,y,z,w) coords 441 | y1 = y0 - j1 + G4 442 | z1 = z0 - k1 + G4 443 | w1 = w0 - l1 + G4 444 | x2 = x0 - i2 + 2.0*G4 # Offsets for third corner in (x,y,z,w) coords 445 | y2 = y0 - j2 + 2.0*G4 446 | z2 = z0 - k2 + 2.0*G4 447 | w2 = w0 - l2 + 2.0*G4 448 | x3 = x0 - i3 + 3.0*G4 # Offsets for fourth corner in (x,y,z,w) coords 449 | y3 = y0 - j3 + 3.0*G4 450 | z3 = z0 - k3 + 3.0*G4 451 | w3 = w0 - l3 + 3.0*G4 452 | x4 = x0 - 1.0 + 4.0*G4 # Offsets for last corner in (x,y,z,w) coords 453 | y4 = y0 - 1.0 + 4.0*G4 454 | z4 = z0 - 1.0 + 4.0*G4 455 | w4 = w0 - 1.0 + 4.0*G4 456 | 457 | # Work out the hashed gradient indices of the five simplex corners 458 | ii = int(i) & 255 459 | jj = int(j) & 255 460 | kk = int(k) & 255 461 | ll = int(l) & 255 462 | gi0 = _perm[ii+_perm[jj+_perm[kk+_perm[ll]]]] % 32 463 | gi1 = _perm[ii+i1+_perm[jj+j1+_perm[kk+k1+_perm[ll+l1]]]] % 32 464 | gi2 = _perm[ii+i2+_perm[jj+j2+_perm[kk+k2+_perm[ll+l2]]]] % 32 465 | gi3 = _perm[ii+i3+_perm[jj+j3+_perm[kk+k3+_perm[ll+l3]]]] % 32 466 | gi4 = _perm[ii+1+_perm[jj+1+_perm[kk+1+_perm[ll+1]]]] % 32 467 | 468 | # Calculate the contribution from the five corners 469 | t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0 470 | if t0 < 0: 471 | n0 = 0.0 472 | else: 473 | t0 *= t0 474 | n0 = t0 * t0 * dot4d(_grad4[gi0], x0, y0, z0, w0) 475 | 476 | t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1 477 | if t1 < 0: 478 | n1 = 0.0 479 | else: 480 | t1 *= t1 481 | n1 = t1 * t1 * dot4d(_grad4[gi1], x1, y1, z1, w1) 482 | 483 | t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2 484 | if t2 < 0: 485 | n2 = 0.0 486 | else: 487 | t2 *= t2 488 | n2 = t2 * t2 * dot4d(_grad4[gi2], x2, y2, z2, w2) 489 | 490 | t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3 491 | if t3 < 0: 492 | n3 = 0.0 493 | else: 494 | t3 *= t3 495 | n3 = t3 * t3 * dot4d(_grad4[gi3], x3, y3, z3, w3) 496 | 497 | t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4 498 | if t4 < 0: 499 | n4 = 0.0 500 | else: 501 | t4 *= t4 502 | n4 = t4 * t4 * dot4d(_grad4[gi4], x4, y4, z4, w4) 503 | 504 | # Sum up and scale the result to cover the range [-1,1] 505 | return 27.0 * (n0 + n1 + n2 + n3 + n4) 506 | 507 | 508 | def dot2d(g, x, y): 509 | return g[0]*x + g[1]*y 510 | 511 | def dot3d(g, x, y, z): 512 | return g[0]*x + g[1]*y + g[2]*z 513 | 514 | def dot4d(g, x, y, z, w): 515 | return g[0]*x + g[1]*y + g[2]*z + g[3]*w 516 | 517 | 518 | """The gradients are the midpoints of the vertices of a cube.""" 519 | _grad3 = [ 520 | [1,1,0], [-1,1,0], [1,-1,0], [-1,-1,0], 521 | [1,0,1], [-1,0,1], [1,0,-1], [-1,0,-1], 522 | [0,1,1], [0,-1,1], [0,1,-1], [0,-1,-1] 523 | ] 524 | 525 | """The gradients are the midpoints of the vertices of a cube.""" 526 | _grad4 = [ 527 | [0,1,1,1], [0,1,1,-1], [0,1,-1,1], [0,1,-1,-1], 528 | [0,-1,1,1], [0,-1,1,-1], [0,-1,-1,1], [0,-1,-1,-1], 529 | [1,0,1,1], [1,0,1,-1], [1,0,-1,1], [1,0,-1,-1], 530 | [-1,0,1,1], [-1,0,1,-1], [-1,0,-1,1], [-1,0,-1,-1], 531 | [1,1,0,1], [1,1,0,-1], [1,-1,0,1], [1,-1,0,-1], 532 | [-1,1,0,1], [-1,1,0,-1], [-1,-1,0,1], [-1,-1,0,-1], 533 | [1,1,1,0], [1,1,-1,0], [1,-1,1,0], [1,-1,-1,0], 534 | [-1,1,1,0], [-1,1,-1,0], [-1,-1,1,0], [-1,-1,-1,0] 535 | ] 536 | 537 | """Permutation table. The same list is repeated twice.""" 538 | _perm = [ 539 | 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 540 | 8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 541 | 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71, 542 | 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 543 | 55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89, 544 | 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226, 545 | 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 546 | 189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43, 547 | 172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97, 548 | 228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239, 549 | 107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254, 550 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, 551 | 552 | 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 553 | 8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 554 | 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71, 555 | 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 556 | 55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89, 557 | 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226, 558 | 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 559 | 189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43, 560 | 172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97, 561 | 228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239, 562 | 107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254, 563 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 564 | ] 565 | 566 | """A lookup table to traverse the simplex around a given point in 4D.""" 567 | _simplex = [ 568 | [0,1,2,3],[0,1,3,2],[0,0,0,0],[0,2,3,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,2,3,0], 569 | [0,2,1,3],[0,0,0,0],[0,3,1,2],[0,3,2,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,3,2,0], 570 | [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 571 | [1,2,0,3],[0,0,0,0],[1,3,0,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,3,0,1],[2,3,1,0], 572 | [1,0,2,3],[1,0,3,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,0,3,1],[0,0,0,0],[2,1,3,0], 573 | [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 574 | [2,0,1,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,0,1,2],[3,0,2,1],[0,0,0,0],[3,1,2,0], 575 | [2,1,0,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,1,0,2],[0,0,0,0],[3,2,0,1],[3,2,1,0] 576 | ] --------------------------------------------------------------------------------