├── .gitignore
├── .DS_Store
├── favicon.ico
├── img
└── cam.png
├── index.js
├── webpack.config.js
├── present.html
├── package.json
├── README.md
├── index.html
├── defaultShaders.js
├── edit.html
├── camera.js
├── lint.css
├── style.css
├── fragmentShader.js copy
└── editor.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.swp
3 | .env
4 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharStiles/shaderplace/HEAD/.DS_Store
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharStiles/shaderplace/HEAD/favicon.ico
--------------------------------------------------------------------------------
/img/cam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharStiles/shaderplace/HEAD/img/cam.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | window.onload = (event) => {
2 | var editButton = document.getElementById("editButton");
3 | editButton.onclick = sendToEditor;
4 | var presentButton = document.getElementById("presentButton");
5 | presentButton.onclick = sendToPresenter;
6 | }
7 |
8 | function sendToEditor() {
9 | var room = document.getElementById("room").value;
10 | window.location.href = "./edit.html?room=" + room;
11 | }
12 |
13 | function sendToPresenter() {
14 | var room = document.getElementById("room").value;
15 | window.location.href = "./present.html?room=" + room;
16 | }
17 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'development',
5 | devtool: 'source-map',
6 | entry: {
7 | codemirror: './editor.js',
8 | index: './index.js'
9 | },
10 | output: {
11 | globalObject: 'self',
12 | path: path.resolve(__dirname, './dist/'),
13 | filename: '[name].bundle.js',
14 | publicPath: '/editor/dist/'
15 | },
16 | devServer: {
17 | contentBase: path.join(__dirname),
18 | compress: true,
19 | inline:true,
20 | disableHostCheck: true,
21 | watchContentBase: true,
22 | publicPath: '/dist/',
23 | historyApiFallback: {
24 | index: 'index.html'
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/present.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shader.Place
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Oh no! Your browser doesn't support canvas!
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shader.place",
3 | "version": "0.0.2",
4 | "description": "❤ a place for shader collaboration ❤",
5 | "scripts": {
6 | "dist": "webpack --mode=production --watch",
7 | "start": "webpack-dev-server --open-page index.html"
8 | },
9 | "author": "Char Stiles ",
10 | "license": "Anti-Capitalist Software License",
11 | "dependencies": {
12 | "@codemirror/lint": "^0.18.2",
13 | "@opentok/client": "^2.19.3",
14 | "agora-rtc-sdk": "^3.5.2",
15 | "codemirror": "^5.59.1",
16 | "dotenv-webpack": "^8.0.1",
17 | "opentok": "^2.11.0",
18 | "serve": "^14.2.1",
19 | "three": "^0.126.1",
20 | "webpack": "^5.89.0",
21 | "webpack-cli": "^3.3.12",
22 | "webpack-dev-server": "^3.11.1",
23 | "y-codemirror": "^2.0.9",
24 | "y-webrtc": "^10.3.0",
25 | "yjs": "^13.4.12"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shader Place
2 | A realtime collaborative livecode GLSL editor
3 |
4 | ## Requirements
5 | - node.js
6 |
7 | ## Quickstart!
8 | ```
9 | $ git clone https://github.com/CharStiles/shaderplace.git
10 | $ cd shaderplace
11 | $ npm i --legacy-peer-deps
12 | $ npm start
13 | ```
14 | go to http://localhost:8080/
15 |
16 | ## TODO:
17 | - add backend for password, more permanent rooms
18 | - WebRTC implementation
19 |
20 | **High Priority**
21 | - overall profile and analyse efficiency
22 | - undo manager
23 | - carot color
24 | - usernames to be above carot
25 | - add backbuffer texture input
26 | - add webcam texture input (for multiple users)
27 | - add bins to audio input
28 | - menu for info on globals, hot keys and meta info
29 | - hot keys for hiding code
30 | - organize code
31 |
32 | **Nice To have**
33 | - webcam mice??
34 | - input images
35 |
36 | This has been made possible by the STUDIO for Creative Inquiry's Spring 2021 OSSTA Residencies (Open-Source Software Toolkits for the Arts)
37 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shader.Place
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Welcome to Shader.Place
15 |
16 |
17 |
A real-time collaborative GLSL livecode editor
18 |
19 |
20 |
21 |
Go to room (code overlayed)
22 |
Go to room (code adjacent)
23 |
24 |
25 | To make a suggestion, report an issue, contribute or fork go to: https://github.com/CharStiles/shaderplace
26 |
27 | or email contact at charstiles.com
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/defaultShaders.js:
--------------------------------------------------------------------------------
1 | export const _fragmentShaderC = `
2 | #ifdef GL_ES
3 | precision mediump float;
4 | #endif
5 |
6 | uniform vec2 u_resolution;
7 | uniform float u_time;
8 | uniform float u_vol;
9 |
10 | // main is a reserved function that is going to be called first
11 | void main(void)
12 | {
13 | vec2 normCoord = gl_FragCoord.xy/u_resolution;
14 |
15 | float time = u_time/5.0; //slow down time
16 |
17 | vec2 uv = -1. + 2. * normCoord;
18 | float r = sin(time + uv.x);
19 | // x is left to right, why we see red moving from right to left think about us as a camera moving around
20 | // sin returns a number from -1 to 1, and colors are from 0 to 1, so it clips to no red half the time
21 |
22 | float g = sin(-time + uv.y * 20.); // higher frequency green stripes
23 |
24 | float b = mod(uv.x / uv.y,1.0);
25 | // when x is eual to y the colors will be brighter, mod repeats the space
26 | // mod is like a sawtooth function
27 |
28 | vec4 color = vec4(r,g,b,1);
29 | gl_FragColor = color;
30 | }
31 |
32 | `;
33 |
34 | export const _vertexShaderC = `
35 | attribute vec2 aVertexPosition;
36 |
37 | void main() {
38 | gl_Position = vec4(aVertexPosition, 0.0, 1.0);
39 | }
40 |
41 | `;
42 |
43 |
--------------------------------------------------------------------------------
/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shader.Place
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Oh no! Your browser doesn't support canvas!
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/camera.js:
--------------------------------------------------------------------------------
1 | // this script is from cut-ruby.glitch.me
2 |
3 | // so good
4 | var FFT_SIZE = 512;
5 | var vol;
6 |
7 | if (window.isProduction && window.location.protocol !== "https:") {
8 | window.location = "https://" + window.location.hostname;
9 | }
10 |
11 | class Camera {
12 | constructor() {
13 | this.video = document.createElement("video");
14 | this.video.setAttribute("muted", true);
15 | this.video.setAttribute("playsinline", false);
16 |
17 | this.selfie = false;
18 |
19 | this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
20 | }
21 |
22 | _startCapture() {
23 | return navigator.mediaDevices
24 | .getUserMedia({
25 | audio: true,
26 | video: false,
27 | })
28 | .then((stream) => {
29 | this.stream = stream;
30 | var source = this.audioCtx.createMediaStreamSource(stream);
31 |
32 | this.analyser = this.audioCtx.createAnalyser();
33 | this.analyser.smoothingTimeConstant = 0.2;
34 | this.analyser.fftSize = FFT_SIZE;
35 | source.connect(this.analyser);
36 | });
37 | }
38 |
39 | _startCaptureVid() {
40 | return navigator.mediaDevices
41 | .getUserMedia({
42 | audio: false,
43 | video: {
44 | facingMode: this.selfie ? "user" : "environment",
45 | muted: true,
46 | },
47 | })
48 | .then((stream) => {
49 | this.stream = stream;
50 | this.video.srcObject = stream;
51 |
52 | this.video.play();
53 | });
54 | }
55 |
56 | init() {
57 | this._startCapture();
58 | return this._startCaptureVid();
59 | }
60 | flip() {
61 | this.selfie = !this.selfie;
62 | this._startCapture();
63 | }
64 | }
65 |
66 | let button = document.querySelector("button");
67 | let camera = new Camera();
68 | document.querySelector(".vidholder").appendChild(camera.video);
69 |
70 | button.addEventListener("click", function (e) {
71 | camera
72 | .init()
73 | .then(start)
74 | .catch((e) => console.error(e));
75 | });
76 |
77 | (function () {
78 | camera
79 | .init()
80 | .then(start)
81 | .catch((e) => console.error(e));
82 | })();
83 |
84 | function start() {}
--------------------------------------------------------------------------------
/lint.css:
--------------------------------------------------------------------------------
1 | /* CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | Distributed under an MIT license: https://codemirror.net/LICENSE */
3 |
4 | /* The lint marker gutter */
5 | .CodeMirror-lint-markers {
6 | width: 16px;
7 | }
8 |
9 | .CodeMirror-lint-tooltip {
10 | background-color: #ffd;
11 | border: 1px solid black;
12 | border-radius: 4px 4px 4px 4px;
13 | color: black;
14 | font-family: monospace;
15 | font-size: 10pt;
16 | overflow: hidden;
17 | padding: 2px 5px;
18 | position: fixed;
19 | white-space: pre;
20 | white-space: pre-wrap;
21 | z-index: 100;
22 | max-width: 600px;
23 | opacity: 0;
24 | transition: opacity .4s;
25 | -moz-transition: opacity .4s;
26 | -webkit-transition: opacity .4s;
27 | -o-transition: opacity .4s;
28 | -ms-transition: opacity .4s;
29 | }
30 |
31 | .CodeMirror-lint-mark {
32 | background-position: left bottom;
33 | background-repeat: repeat-x;
34 | }
35 |
36 | .CodeMirror-lint-mark-warning {
37 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
38 | }
39 |
40 | .CodeMirror-lint-mark-error {
41 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
42 | }
43 |
44 | .CodeMirror-lint-marker {
45 | background-position: center center;
46 | background-repeat: no-repeat;
47 | cursor: pointer;
48 | display: inline-block;
49 | height: 16px;
50 | width: 16px;
51 | vertical-align: middle;
52 | position: relative;
53 | }
54 |
55 | .CodeMirror-lint-message {
56 | padding-left: 18px;
57 | background-position: top left;
58 | background-repeat: no-repeat;
59 | }
60 |
61 | .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
62 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
63 | }
64 |
65 | .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
66 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
67 | }
68 |
69 | .CodeMirror-lint-marker-multiple {
70 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
71 | background-repeat: no-repeat;
72 | background-position: right bottom;
73 | width: 100%; height: 100%;
74 | }
75 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /* ====================================
2 | GLOBAL STYLES
3 | ======================================= */
4 |
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;
11 | background-color : black;
12 | overflow: hidden;
13 | caret-color: red;
14 | }
15 |
16 | h1,
17 | h2,
18 | h3 {
19 | font-style: italic;
20 | }
21 |
22 | h1 {
23 | color: #d0ff37;
24 | }
25 |
26 | h2 {
27 | color: #dbfa6b;
28 | margin: auto;
29 | width: 50%;
30 | }
31 |
32 | h3 {
33 | color: #d9fdaa;
34 | margin: auto;
35 | width: 50%;
36 | }
37 |
38 | a {
39 | overflow-wrap: break-word;
40 | }
41 |
42 | input {
43 | width: 30%;
44 | display: block;
45 | margin: 0 auto 10px;
46 | padding: 5px;
47 | border: 1px solid lightgrey;
48 | border-radius: 3px;
49 | font-size: 16px;
50 | }
51 |
52 | button {
53 | font-size: 16px;
54 | border-radius: 3px;
55 | background-color: lightgrey;
56 | border: 1px solid grey;
57 | box-shadow: 2px 2px rgb(255, 145, 0);
58 | cursor: pointer;
59 | }
60 |
61 | button:hover {
62 | background-color: yellow;
63 | }
64 |
65 | button:active {
66 | box-shadow: none;
67 | }
68 |
69 | /* ====================================
70 | SCROLLBAR STYLES
71 | =======================================
72 | Only for webkit based browsers.
73 | For mozilla styles go to selector:
74 | .CodeMirror
75 | ======================================= */
76 |
77 | ::-webkit-scrollbar {
78 | width: 11px;
79 | height: 11px;
80 | }
81 |
82 | ::-webkit-scrollbar-button {
83 | width: 0;
84 | height: 0;
85 | }
86 |
87 | ::-webkit-scrollbar-thumb {
88 | border: 1px solid #7e7e7e;
89 | border-radius: 50px;
90 | }
91 |
92 | ::-webkit-scrollbar-track {
93 | border: 0 none #000000;
94 | border-radius: 53px;
95 | }
96 |
97 | ::-webkit-scrollbar-thumb,
98 | ::-webkit-scrollbar-track,
99 | ::-webkit-scrollbar-corner {
100 | background: transparent;
101 | }
102 |
103 | /* ====================================
104 | UTILITY/HELPER CLASSES
105 | ======================================= */
106 |
107 | .u-txtcenter {
108 | text-align: center;
109 | }
110 |
111 | .u-displaywrapper {
112 | height: 100vh;
113 | width: 100%;
114 | }
115 |
116 | /* ====================================
117 | COMPONENTS CLASSES
118 | ======================================= */
119 |
120 | .wrapper {
121 | width: 100%;
122 | height: 100%;
123 | display: flex;
124 | }
125 |
126 | .editor-mode {
127 | width: 85%;
128 | max-height: 100vh;
129 | overflow-y: scroll;
130 | }
131 |
132 | /* ====================================
133 | CodeMirror CUSTOM STYLES
134 | ======================================= */
135 |
136 | .CodeMirror {
137 | width: 100%;
138 | height: 100%;
139 | scrollbar-color: rgba(0.5,0.5,0.5,1.0) rgba(0.5,0.5,0.5,.0); /* Mozilla styles */
140 | }
141 |
142 | .CodeMirror-present {
143 | background-color: rgba(255, 255, 255, 0.0) !important;
144 | text-shadow: 1px 1px #555555;
145 | caret-color: red !important;
146 | position: absolute;
147 | top: 0;
148 | left: 0;
149 | }
150 |
151 | .CodeMirror-editor {
152 | /* ToDo: Change bg color in CodeMirror directly or leave it here? */
153 | background-color: #c5c5c5;
154 | }
155 |
156 | .remote-caret {
157 | position: relative;
158 | border-left: 1px solid black;
159 | border-right: 1px solid black;
160 | margin-left: -1px;
161 | margin-right: -1px;
162 | box-sizing: border-box;
163 | }
164 |
165 | .remote-caret > div {
166 | position: absolute;
167 | top: -1.05em;
168 | left: -1px;
169 | font-size: .6em;
170 | background-color: rgb(250, 129, 0);
171 | font-family: serif;
172 | font-style: normal;
173 | font-weight: normal;
174 | line-height: normal;
175 | user-select: none;
176 | color: white;
177 | padding-left: 2px;
178 | padding-right: 2px;
179 | z-index: 3;
180 | transition: opacity .3s ease-in-out;
181 | }
182 |
183 | .remote-caret.hide-name > div {
184 | transition-delay: .7s;
185 | opacity: 0.5;
186 | }
187 |
188 | .remote-caret:hover > div {
189 | opacity: 1;
190 | transition-delay: 0s;
191 | }
192 |
193 | .cm-searching {
194 | background-color: transparent;
195 | }
196 |
197 | .CodeMirror-gutters {
198 | border-right: 1px solid #000;
199 | background-color: #000000;
200 | }
201 |
202 | .CodeMirror-present.cm-s-default .cm-comment {
203 | color: rgb(255, 255, 255);
204 | mix-blend-mode: difference;
205 | text-shadow: 0 0 5px black, 0 0 15px black;
206 | }
207 |
208 | .CodeMirror-present span[role=presentation] {
209 | background-color: rgba(255, 255, 255, 0.69);
210 | }
211 |
212 | span.cm-tab[role=presentation] {
213 | background-color: transparent;
214 | }
--------------------------------------------------------------------------------
/fragmentShader.js copy:
--------------------------------------------------------------------------------
1 |
2 | var _fragmentShader = `
3 |
4 |
5 | #ifdef GL_ES
6 | precision mediump float;
7 | #endif
8 |
9 | uniform vec2 u_resolution;
10 | uniform vec2 u_mouse;
11 | uniform float u_time;
12 | uniform vec4 u_camRot;
13 | uniform vec4 u_camQuat;
14 | uniform vec3 u_camPos;
15 | uniform float u_vol;
16 | uniform sampler2D u_feed;
17 |
18 | #define PI 3.14159265
19 | #define TAU (2*PI)
20 | #define PHI (sqrt(5)*0.5 + 0.5)
21 | // Define some constants
22 | const int steps = 128; // This is the maximum amount a ray can march.
23 | const float smallNumber = 0.001;
24 | const float maxDist = 30.; // This is the maximum distance a ray can travel.
25 |
26 | float smin( float a, float b, float k )
27 | {
28 | float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
29 | return mix( b, a, h ) - k*h*(1.0-h);
30 | }
31 | vec3 rotateQuat( vec4 quat, vec3 vec )
32 | {
33 | return vec + 2.0 * cross( cross( vec, quat.xyz ) + quat.w * vec, quat.xyz );
34 | }
35 | float random (vec2 st) {
36 | return fract(sin(dot(st.xy,
37 | vec2(12.9898,78.233)))*
38 | 43758.5453123);
39 | }
40 |
41 | vec3 lookAt(vec2 uv, vec3 camOrigin, vec3 camTarget){
42 | vec3 zAxis = normalize(camTarget - camOrigin);
43 | vec3 up = vec3(0,1,0);
44 | vec3 xAxis = normalize(cross(up, zAxis));
45 | vec3 yAxis = normalize(cross(zAxis, xAxis));
46 |
47 | float fov = 2.;
48 |
49 | vec3 dir = (normalize(uv.x * xAxis + uv.y * yAxis + zAxis * fov));
50 |
51 | return dir;
52 | }
53 |
54 |
55 |
56 | float sphere(vec3 p) {
57 | float l = length(p) ;
58 | return l - 1.5 ;
59 | }
60 |
61 | float scene(vec3 position){
62 | float time = u_time;
63 | float ground = position.y - sin(position.x*10.)/10. - sin((cos(time/30.) + position.z*2. -time)) / 1. - (0.+ (pow(length(position),.10)*20.0 )- 20.0) + 6.;
64 |
65 | float b = sphere(vec3(
66 | position.x,
67 | position.y,
68 | position.z - 10.)
69 | );
70 |
71 |
72 | return smin(b,ground,0.2);
73 | }
74 |
75 | vec3 estimateNormal(vec3 p) {
76 | float smallNumber = 0.002;
77 | vec3 n = vec3(
78 | scene(vec3(p.x + smallNumber, p.yz)) -
79 | scene(vec3(p.x - smallNumber, p.yz)),
80 | scene(vec3(p.x, p.y + smallNumber, p.z)) -
81 | scene(vec3(p.x, p.y - smallNumber, p.z)),
82 | scene(vec3(p.xy, p.z + smallNumber)) -
83 | scene(vec3(p.xy, p.z - smallNumber))
84 | );
85 | // poke around the point to get the line perpandicular
86 | // to the surface at p, a point in space.
87 | return normalize(n);
88 | }
89 | vec3 hsv2rgb(vec3 c) {
90 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
91 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
92 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
93 | }
94 |
95 | vec4 lighting2(vec3 pos, vec3 viewDir){
96 | vec3 lightPos = vec3(cos(u_time)*0.23,0.3,sin(u_time)*0.2);
97 | // light moves left to right
98 | vec3 normal = estimateNormal(pos);
99 | vec3 reflectDir = reflect(-lightPos, normal);
100 |
101 | float specularStrength =5500.;
102 | vec3 specColor = hsv2rgb( vec3((((pos.y)*tan(pos.x)*10.)/normal.z )/700.,normal.x,.041));
103 | float spec = pow( max(dot(viewDir, reflectDir), -0.80), 6.);
104 | vec3 specular = specularStrength * spec * specColor;
105 | return vec4(specular,1.);
106 |
107 | }
108 | vec4 trace (vec3 origin, vec3 direction){
109 |
110 | float dist = 0.;
111 | float totalDistance = 0.;
112 | vec3 positionOnRay = origin;
113 |
114 | for(int i = 0 ; i < steps; i++){
115 |
116 | dist = scene(positionOnRay);
117 |
118 | // Advance along the ray trajectory the amount that we know the ray
119 | // can travel without going through an object.
120 | positionOnRay += dist * direction + random(positionOnRay.xy)*0.1;
121 |
122 | // Total distance is keeping track of how much the ray has traveled
123 | // thus far.
124 | totalDistance += dist;
125 |
126 | // If we hit an object or are close enough to an object,
127 | if (dist < smallNumber){
128 | // return the distance the ray had to travel normalized so be white
129 | // at the front and black in the back.
130 | return lighting2(positionOnRay, direction);//1. - (vec4(totalDistance) / maxDist);
131 |
132 | }
133 |
134 | if (totalDistance > maxDist){
135 |
136 | return texture2D(u_feed, gl_FragCoord.xy/u_resolution); // Background color.
137 | }
138 | }
139 |
140 | return texture2D(u_feed, gl_FragCoord.xy/u_resolution);
141 | }
142 |
143 | // main is a reserved function that is going to be called first
144 | void main(void)
145 | {
146 | vec2 normCoord = gl_FragCoord.xy/u_resolution;
147 |
148 | vec2 uv = -1. + 2. * normCoord;
149 | // Unfortunately our screens are not square so we must account for that.
150 | uv.x *= (u_resolution.x / u_resolution.y);
151 |
152 | vec3 rayOrigin = vec3(uv, 0.);
153 | vec3 camOrigin = u_camPos; //vec3(0., 0., -1.);
154 |
155 |
156 |
157 | vec3 zAxis = vec3(0,0,1);
158 | vec3 up = vec3(0,1,0);
159 | vec3 xAxis = normalize(cross(up, zAxis));
160 | vec3 yAxis = normalize(cross(zAxis, xAxis));
161 |
162 | // we need to apply rotate 3 times each with rotation on the relative object,
163 | // then we can get the lookat direction that we need. SO lets start with looking at forward
164 |
165 | vec3 dirToLook = zAxis;//normalize(camOrigin + rayOrigin);
166 | dirToLook = rotateQuat(u_camQuat,dirToLook);
167 |
168 |
169 | // according to 3js docs Default order is 'XYZ'
170 |
171 | vec3 dir = lookAt(uv, camOrigin, dirToLook);
172 |
173 |
174 | gl_FragColor = max(trace(camOrigin, dir),0.2);
175 | }
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | `;
209 |
--------------------------------------------------------------------------------
/editor.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 |
3 | // @ts-ignore
4 | import CodeMirror from "codemirror";
5 | import * as Y from "yjs";
6 | import { WebsocketProvider } from "y-websocket";
7 | //import { WebrtcProvider } from 'y-webrtc'
8 | import { CodemirrorBinding } from "y-codemirror";
9 | import "codemirror/mode/clike/clike.js";
10 | import 'codemirror/addon/lint/lint';
11 | import {_fragmentShaderC, _vertexShaderC} from "./defaultShaders.js";
12 |
13 | // Element storage
14 | var gl;
15 | var editor;
16 | let glCanvas = null;
17 | let _fragmentShader = _fragmentShaderC;
18 |
19 | // Current state storage
20 | var isDirty = false;
21 | let shaderProgram;
22 |
23 | // Aspect ratio and coordinate system
24 | // details
25 | let aspectRatio;
26 | let resolution;
27 |
28 | // Vertex information
29 | let vertexArray;
30 | let vertexBuffer;
31 | let vertexNumComponents;
32 | let vertexCount;
33 |
34 | // Rendering data shared with the
35 | // scalers.
36 | let uResolution;
37 | let uTime;
38 | let uVol;
39 | let aVertexPosition;
40 |
41 | // Animation timing
42 | let previousTime = 0.0;
43 | // this script is from cut-ruby.glitch.me
44 |
45 | // so good
46 | var FFT_SIZE = 512;
47 | var vol;
48 |
49 | if (window.isProduction && window.location.protocol !== "https:") {
50 | window.location = "https://" + window.location.hostname;
51 | }
52 |
53 | class Camera {
54 | constructor() {
55 | this.video = document.createElement("video");
56 | this.video.setAttribute("muted", true);
57 | this.video.setAttribute("playsinline", false);
58 |
59 | this.selfie = false;
60 |
61 | this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
62 | }
63 |
64 | _startCapture() {
65 | return navigator.mediaDevices
66 | .getUserMedia({
67 | audio: true,
68 | video: false,
69 | })
70 | .then((stream) => {
71 | this.stream = stream;
72 | var source = this.audioCtx.createMediaStreamSource(stream);
73 |
74 | this.analyser = this.audioCtx.createAnalyser();
75 | this.analyser.smoothingTimeConstant = 0.9;
76 | this.analyser.fftSize = FFT_SIZE;
77 | source.connect(this.analyser);
78 | });
79 | }
80 | init() {
81 | this._startCapture();
82 | return this._startCapture();
83 | }
84 | flip() {
85 | this.selfie = !this.selfie;
86 | this._startCapture();
87 | }
88 | }
89 |
90 | let button = document.querySelector("button");
91 | let camera;// new Camera();
92 | //document.querySelector("body").appendChild(camera.video);
93 |
94 | // document.addEventListener("click", function (e) {
95 | // camera
96 | // .init()
97 | // .then(start)
98 | // .catch((e) => console.error(e));
99 | // });
100 |
101 | // (function () {
102 | // camera
103 | // .init()
104 | // .then(start)
105 | // .catch((e) => console.error(e));
106 | // })();
107 |
108 | function start() {}
109 |
110 |
111 |
112 | // from here https://hackernoon.com/creative-coding-using-the-microphone-to-make-sound-reactive-art-part1-164fd3d972f3
113 | // A more accurate way to get overall volume
114 | function getRMS(spectrum) {
115 | var rms = 0;
116 | for (var i = 0; i < spectrum.length; i++) {
117 | rms += spectrum[i] * spectrum[i];
118 | }
119 | rms /= spectrum.length;
120 | rms = Math.sqrt(rms);
121 | let norm = rms / 128;
122 | return (norm - 0.99) * 100;
123 | }
124 |
125 | function isInPresentationMode() {
126 | if (window.location.pathname.split('/').pop() == 'present.html') {
127 | return true;
128 | }
129 | return false;
130 | }
131 |
132 | function isLockedPresent(){
133 | const queryString = window.location.search;
134 | console.log(queryString);
135 | const urlParams = new URLSearchParams(queryString);
136 | const pw = urlParams.get('pw')
137 | const room = urlParams.get('room')
138 | console.log(room)
139 | // if you hack into my classroom I will cry in front of everyone :,(
140 | if(pw != "WhyDoesDotEnvEvadeMe" && room.toLowerCase()== "classroom"){
141 | return true;
142 | console.log("youre a student, in student mode")
143 | }
144 |
145 | return false;
146 | }
147 |
148 | function addCodeMirrorPresentModifier() {
149 | const codeMirrorDiv = document.querySelector(".CodeMirror");
150 | if (codeMirrorDiv) {
151 | codeMirrorDiv.classList.add("CodeMirror-present");
152 | }
153 | }
154 |
155 | function addCodeMirrorEditorModifier() {
156 | const codeMirrorDiv = document.querySelector(".CodeMirror");
157 | if (codeMirrorDiv) codeMirrorDiv.classList.add("CodeMirror-editor");
158 | }
159 |
160 | function initYdoc() {
161 | const ydoc = new Y.Doc();
162 |
163 | const searchParams = new URLSearchParams(window.location.search);
164 | var room = "";
165 | if (searchParams.has("room")){
166 | room = searchParams.get("room");
167 | }
168 |
169 | const provider = new WebsocketProvider(
170 | `ws${location.protocol.slice(4)}//${location.host}/ws`,
171 | room,
172 | ydoc
173 | );
174 | // const provider = new WebsocketProvider('wss://localhost:8080', room, ydoc, { WebSocketPolyfill: require('ws') })
175 |
176 | var editorContainer = document.getElementById("editor");
177 | editor = CodeMirror(editorContainer, {
178 | value: _fragmentShader,
179 | lineNumbers: true,
180 | mode: "x-shader/x-vertex",
181 | gutters: ["CodeMirror-lint-markers"],
182 | lint: true,
183 | lineWrapping: !isInPresentationMode(),
184 | readOnly: isLockedPresent()
185 | //editable: false //!isLockedPresent()
186 | });
187 |
188 | const ytext = ydoc.getText("codemirror");
189 | // const undoManager = new Y.UndoManager(ytext, { trackedOrigins: new Set([ydoc.clientID]) })
190 | const binding = new CodemirrorBinding(ytext, editor, provider.awareness);
191 | const setDefaultVal = () => {
192 | if (ytext.toString() === "") {
193 | ytext.insert(0, _fragmentShader);
194 | }
195 | };
196 |
197 |
198 | if (provider.sync) {
199 | setDefaultVal();
200 | } else {
201 | provider.on("sync", setDefaultVal);
202 | }
203 |
204 | setDefaultVal();
205 | // editor.getDoc().markText(
206 | // {
207 | // line: 5,
208 | // ch: 1
209 | // },
210 | // {
211 | // line: 50,
212 | // ch: 3
213 | // },
214 | // {
215 | // css: "color : red"
216 | // }
217 | // );
218 |
219 | if (isInPresentationMode()) {
220 | addCodeMirrorPresentModifier();
221 | } else {
222 | addCodeMirrorEditorModifier();
223 | }
224 |
225 | // @ts-ignore
226 | window.example = { provider, ydoc, ytext, binding, Y };
227 | }
228 |
229 |
230 | // this function will trigger a change to the editor
231 | function onEdit() {
232 | const fragmentCode = editor.getValue();
233 | updateShader(fragmentCode);
234 | }
235 |
236 | function updateShader(fragmentCode) {
237 | if (!checkFragmentShader(fragmentCode)) {
238 | return;
239 | }
240 |
241 | _fragmentShader = fragmentCode;
242 |
243 | isDirty = true;
244 | }
245 |
246 | window.onload = (event) => {
247 | webgl_startup();
248 | initYdoc();
249 | }
250 |
251 | function animateScene() {
252 | gl.viewport(0, 0, glCanvas.width, glCanvas.height);
253 | // This sets background color
254 | gl.clearColor(1, 1, 1, 1);
255 | gl.clear(gl.COLOR_BUFFER_BIT);
256 |
257 | gl.useProgram(shaderProgram);
258 |
259 | uResolution =
260 | gl.getUniformLocation(shaderProgram, "u_resolution");
261 | uTime =
262 | gl.getUniformLocation(shaderProgram, "u_time");
263 | uVol =
264 | gl.getUniformLocation(shaderProgram, "u_vol");
265 |
266 | gl.uniform2fv(uResolution, resolution);
267 | gl.uniform1f(uTime, previousTime);
268 | if (camera && camera.analyser) {
269 | var bufferLength = camera.analyser.frequencyBinCount;
270 | var dataArray = new Uint8Array(bufferLength);
271 |
272 | camera.analyser.getByteTimeDomainData(dataArray);
273 | gl.uniform1f(uVol, getRMS(dataArray));
274 | }
275 | else{
276 | gl.uniform1f(uVol, 0.0);
277 | }
278 |
279 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
280 |
281 | aVertexPosition =
282 | gl.getAttribLocation(shaderProgram, "aVertexPosition");
283 |
284 | gl.enableVertexAttribArray(aVertexPosition);
285 | gl.vertexAttribPointer(aVertexPosition, vertexNumComponents,
286 | gl.FLOAT, false, 0, 0);
287 |
288 | gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
289 |
290 | window.requestAnimationFrame(function(currentTime) {
291 | previousTime = previousTime + .05;
292 | // TODO here check dirty bit and recompile?
293 | if (isDirty) {
294 | // recompile and clear dirty bit
295 | shaderProgram = buildShaderProgram();
296 | isDirty = false;
297 | }
298 | animateScene();
299 | });
300 | }
301 |
302 | function compileShader(type, code) {
303 | let shader = gl.createShader(type);
304 |
305 | gl.shaderSource(shader, code);
306 | gl.compileShader(shader);
307 |
308 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
309 | console.log(`Error compiling ${type === gl.VERTEX_SHADER ? "vertex" : "fragment"} shader:`);
310 | console.log(gl.getShaderInfoLog(shader));
311 | }
312 | return shader;
313 | }
314 |
315 |
316 | function buildShaderProgram() {
317 | let program = gl.createProgram();
318 |
319 | // Compile vertex shader
320 | let shader = compileShader(gl.VERTEX_SHADER, vertexShader());
321 | gl.attachShader(program, shader);
322 |
323 | // Compile fragment shader
324 | shader = compileShader(gl.FRAGMENT_SHADER, fragmentShader());
325 | gl.attachShader(program, shader);
326 |
327 | gl.linkProgram(program)
328 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
329 | console.log("Error linking shader program:");
330 | console.log(gl.getProgramInfoLog(program));
331 | }
332 |
333 | return program;
334 | }
335 |
336 | function webgl_startup() {
337 | glCanvas = document.getElementById("glcanvas");
338 | if (glCanvas.width != glCanvas.clientWidth) {
339 | glCanvas.width = glCanvas.clientWidth;
340 | }
341 | if (glCanvas.height != glCanvas.clientHeight) {
342 | glCanvas.height = glCanvas.clientHeight;
343 | }
344 | gl = glCanvas.getContext("webgl");
345 |
346 | shaderProgram = buildShaderProgram();
347 |
348 | aspectRatio = glCanvas.width/glCanvas.height;
349 | resolution = [glCanvas.width, glCanvas.height];
350 |
351 | vertexArray = new Float32Array([
352 | -1, 1,
353 | 1, 1,
354 | 1, -1,
355 | -1, 1,
356 | 1, -1,
357 | -1, -1
358 | ]);
359 |
360 | vertexBuffer = gl.createBuffer();
361 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
362 | gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW);
363 |
364 | vertexNumComponents = 2;
365 | vertexCount = vertexArray.length/vertexNumComponents;
366 |
367 | animateScene();
368 | }
369 |
370 |
371 | function vertexShader() {
372 | return _vertexShaderC;
373 | }
374 |
375 | function fragmentShader() {
376 | return _fragmentShader;
377 | }
378 |
379 | // this returns false if the fragment shader cannot compile
380 | // true if it can
381 | function checkFragmentShader(shaderCode, lint = false) {
382 | if (!gl) {
383 | return;
384 | }
385 | let shader = gl.createShader(gl.FRAGMENT_SHADER);
386 | gl.shaderSource(shader, shaderCode);
387 | gl.compileShader(shader);
388 | let infoLog = gl.getShaderInfoLog(shader);
389 | let result = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
390 | let ret = [];
391 | if (!result) {
392 | console.log(infoLog);
393 | var errors = infoLog.split(/\r|\n/);
394 | for (let error of errors){
395 | var splitResult = error.split(":")
396 | ret.push( {
397 | message: splitResult[3] + splitResult[4],
398 | character: splitResult[1],
399 | line: splitResult[2]
400 | })
401 | }
402 | }
403 |
404 | if (result) {
405 | console.log("did update");
406 | _fragmentShader = shaderCode;
407 | isDirty = true;
408 | }
409 |
410 | return ret;
411 | }
412 |
413 |
414 | (function(mod) {
415 | mod(CodeMirror);
416 | })(function(CodeMirror) {
417 | "use strict";
418 |
419 | function validator(text, options) {
420 | var result = [];
421 | var errors = checkFragmentShader(text, true);
422 | if (errors) parseErrors(errors, result);
423 | return result;
424 | }
425 |
426 | CodeMirror.registerHelper("lint", "x-shader/x-vertex", validator);
427 |
428 | function parseErrors(errors, output) {
429 | for ( var i = 0; i < errors.length; i++) {
430 | var error = errors[i];
431 | if (error) {
432 | if (Number(error.line) <= 0) {
433 | console.warn("Cannot display error (invalid line " + error.line + ")", error);
434 | continue;
435 | }
436 |
437 | var start = error.character - 1, end = start + 1;
438 |
439 |
440 | // Convert to format expected by validation service
441 | var hint = {
442 | message: error.message,
443 | severity: "error",
444 | from: CodeMirror.Pos(Number(error.line) - 1, start),
445 | to: CodeMirror.Pos(Number(error.line) - 1, end)
446 | };
447 |
448 | output.push(hint);
449 | }
450 | }
451 | }
452 | });
453 |
--------------------------------------------------------------------------------