├── .gitattributes ├── README.md ├── demos ├── 1.html ├── 2.html ├── 3.html ├── 4.html ├── 5.html ├── 6.html ├── 7.html ├── 8.html ├── a.svg ├── brick.png ├── bricktexture.js ├── castle.html ├── cubemap.png ├── freerotation.html ├── jscolor.js ├── mario.js ├── mario.png ├── mario2.png ├── mariotexture.js ├── sky.jpg ├── tree.png ├── treetexture.js ├── yoshi.js └── yoshi.png ├── ect-0.8.3.exe ├── index.html ├── obj2js ├── 3d-model.obj ├── coin.obj ├── cube.obj ├── index.html ├── mario.mtl ├── mario.obj ├── mario.png ├── mario2.png ├── parse.js ├── wolf.obj ├── yoshi.mtl ├── yoshi.obj └── yoshi.png ├── package.json ├── w.js ├── w.min.full.js ├── w.min.full.zip ├── w.min.lite.js └── w.min.lite.zip /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # W 2 | A micro WebGL2 framework with a ton of features 3 | 4 | https://xem.github.io/W 5 | 6 | license: public domain 7 | 8 | Install via NPM with `npm install https://github.com/xem/W/#semver:1.0.1` 9 | -------------------------------------------------------------------------------- /demos/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
X
9 |
Y
10 |
Z
11 |
0
12 | 13 | 14 |
15 | 16 |
W.reset(canvas);
 17 | W.camera({x:0,y:0,z:0});
 18 | W.light({x:0,y:-1,z:0});
 19 | W.ambient(0.2);
 20 | W.clearColor("#FFFFFF");
21 | 22 |

Camera 23 |

24 | x 25 | y 26 | z 27 |
28 | rx 29 | ry 30 | rz 31 |
32 | fov   33 |

Directional light 34 |

35 | x 36 | y 37 | z 38 |
39 |

Ambient light 40 |

41 | 42 |
43 |

Clear color 44 |

45 |

46 | 47 | 106 | 107 | 119 | 120 | 121 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /demos/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 |
W.group({n:"G"});
 14 | W.plane({g:"G",x:-1,y:1});
 15 | W.billboard({g:"G",x:1,y:1});
 16 | W.cube({g:"G",x:-1.5,y:-1});
 17 | W.pyramid({g:"G",x:0,y:-1});
 18 | W.sphere({g:"G",x:1.5,y:-1});
19 |

20 |

25 |

26 | Settings 27 |

28 | Size: 29 |

30 | rx: 31 |

32 | Background 33 |

34 |   35 |

brick

Mix: 37 |

38 | Rendering 39 |

40 | Draw mode: 41 |

42 | Shading: 43 | 44 | 45 | 46 | 47 |

48 | 49 | 99 | 100 | 112 | 113 | 114 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /demos/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | HTML 14 |
<img src='mario.png' id=mario hidden>
15 | 16 |

17 | JS 18 | 19 |

W.add("mario", {vertices: [...], uv: [...], indices: [...]});
 20 | W.camera({z:4});
 21 | W.light({x:.5,y:0,z:-.5});
 22 | W.mario({n:"M",size:3,t:mario});
23 | 24 |

25 | 26 | Background 27 |

28 | 29 |

30 |

Mix: 31 | 32 | 33 |

34 | Shading 35 |

36 | 37 | 38 | 39 | 40 | 41 |

42 | 43 | 73 | 74 | 86 | 87 | 88 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /demos/4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 |
W.move({n:"M",size:3,rx:0,ry:0,rz:0,a:1000},100);
14 | Settings 15 |

16 | Size 17 |

18 | rx 19 |

20 | ry 21 |

22 | rz 23 |

24 | Animation 25 |

26 | Delay 27 |

28 | 29 | 30 |

31 | 32 | 33 |

34 | 35 | 59 | 60 | 72 | 73 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /demos/5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 |
orthogonal = ({left, right, top, bottom, near, far }) => {
 15 |   return new DOMMatrix([
 16 |     2/(right - left), 0, 0, 0,
 17 |     0, 2/(top - bottom), 0, 0,
 18 |     0, 0, -2/(far - near), 0,
 19 |     -(right + left)/(right - left), -(top + bottom)/(top - bottom),
 20 |     -(far + near)/(far - near), 1
 21 |   ]);
 22 | }
 23 | 
 24 | 
 25 | W.camera({x:4.5,y:2,z:0,rx:-20,ry:30,fov:22});
 26 | 
27 | Projection 28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

37 | 38 | 115 | 116 | 127 | 128 | 129 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /demos/6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 15 | 20 |
13 | 14 |
16 | 17 | 18 | 19 |
21 | 22 |
23 |
24 | 25 |
keys = {u:0, l:0, r:0, d:0};
 26 | W.camera({z:2});
 27 | W.light({z:-.5});
 28 | W.cube({n:"cube", b:"5af"});
 29 | t = new DOMMatrix();
 30 | onmousedown = e => keys[e.target.id] = 1;
 31 | onmouseup = e => keys[e.target.id] = 0;
 32 | setInterval(()=>{
 33 |   let axis = new DOMPoint(
 34 |     rx = keys.d - keys.u,
 35 |     ry = keys.r - keys.l,
 36 |     0
 37 |   );
 38 |   let newAxis = axis.matrixTransform((new DOMMatrix(t)).invertSelf());
 39 |   t.rotateAxisAngleSelf(
 40 |     newAxis.x, newAxis.y, newAxis.z, Math.hypot(rx, ry) * 2
 41 |   );
 42 |   W.move({n:"cube", M: t});
 43 | }, 16);
44 | 45 |
46 | 47 | 74 | 75 | 86 | 87 | 88 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /demos/7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 16 | 21 |
14 | 15 |
17 | 18 | 19 | 20 |
22 | 23 |
24 |
25 | 26 |
keys = {u:0, l:0, r:0, d:0};
 27 | X = -1.5;
 28 | Z = 18;
 29 | RY = 0;
 30 | W.clearColor("8af");
 31 | for(i = 0; i < 10; i++)
 32 |   for(j = 0; j < 10; j++)
 33 |     W.billboard({size:3,x:(i-5)*5,z:(j-5)*5,t:tree,ns:1});
 34 | W.mario({n:"M",size:1,x:X,y:-.9,z:Z,t:"mario",s:1});
 35 | W.camera({g:"M",y:.5,z:-2.5});
 36 | W.plane({g:"camera",size:100,b:"3d2",z:-100,y:-50,ns:1});
 37 | setInterval(()=>{
 38 |   if(keys.u || keys.d)
 39 |     W.move({
 40 |       n:"M",
 41 |       z: Z += (keys.d - keys.u) * Math.cos(RY*Math.PI/180) / 40,
 42 |       x: X += (keys.d - keys.u) * Math.sin(RY*Math.PI/180) / 40
 43 |     });
 44 |   if(keys.r || keys.l) RY += (keys.l - keys.r)/5, W.move({n:"M", ry: RY});
 45 | });
46 | 47 |
48 | 49 | 94 | 95 | 107 | 108 | 109 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /demos/8.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 |
// x, y, z: relative coordinates inside the group
13 | W.move({n:"ball",g:"M",x:.9,y:.2,z:.35});
14 | 15 |

16 |
// x, y, z: World coordinates
17 | W.move({n:"ball",g:null,x:0,y:-.3,z:0});
18 | 19 |
20 | 21 | 51 | 52 | 64 | 65 | 66 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /demos/a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | Hi 😎 22 | -------------------------------------------------------------------------------- /demos/brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/brick.png -------------------------------------------------------------------------------- /demos/castle.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 67 | 68 | -------------------------------------------------------------------------------- /demos/cubemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/cubemap.png -------------------------------------------------------------------------------- /demos/freerotation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 | 8 | 46 | 47 | -------------------------------------------------------------------------------- /demos/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/mario.png -------------------------------------------------------------------------------- /demos/mario2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/mario2.png -------------------------------------------------------------------------------- /demos/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/sky.jpg -------------------------------------------------------------------------------- /demos/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/tree.png -------------------------------------------------------------------------------- /demos/yoshi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/demos/yoshi.png -------------------------------------------------------------------------------- /ect-0.8.3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/ect-0.8.3.exe -------------------------------------------------------------------------------- /obj2js/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.60 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -1.000000 10 | v 1.000000 1.000000 1.000000 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | usemtl Material 14 | f 1 2 3 4 15 | f 5 8 7 6 16 | f 2 6 7 3 17 | f 3 7 8 4 18 | f 5 1 4 8 19 | #usemtl Material.001 20 | f 1 5 6 2 21 | -------------------------------------------------------------------------------- /obj2js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

OBJ to WebGL buffer converter

4 | OBJ file: or 5 |

Options: float range , 6 | max decimals (vertices): , 7 | max decimals (textures): , 8 | Unicode precision: , Indexed 9 | Smooth Animated 10 | 11 |

12 | 13 |
Original (0 bytes)floats (0 bytes)Unicode (0 bytes) 14 |
15 |
Preview: original / floats / Unicode 16 |
17 |
18 |

Original: direct copy of the OBJ values. Floats / Unicode: vertices centered on the origin + lossy compression + model size = 1 19 | 20 | 183 | 191 | -------------------------------------------------------------------------------- /obj2js/mario.mtl: -------------------------------------------------------------------------------- 1 | # Blender3D MTL File: mario_yoshi.blend 2 | # Material Count: 1 3 | newmtl mario 4 | Ns 0.000000 5 | Ka 0.000000 0.000000 0.000000 6 | Kd 0.800000 0.800000 0.800000 7 | Ks 0.000000 0.000000 0.000000 8 | Ni 1.000000 9 | d 1.000000 10 | illum 0 11 | map_Kd ./mario.png 12 | 13 | 14 | -------------------------------------------------------------------------------- /obj2js/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/obj2js/mario.png -------------------------------------------------------------------------------- /obj2js/mario2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/obj2js/mario2.png -------------------------------------------------------------------------------- /obj2js/parse.js: -------------------------------------------------------------------------------- 1 | // parseOBJ 2 | // ======== 3 | // 4 | // This function is a minimal OBJ file loader and parser for WebGL. 5 | // A more complete OBJ/MTL parser is availale on https://github.com/xem/webgl-guide 6 | // It takes the content of an OBJ file path as parameter and returns a hierarchy of objects and groups: 7 | // return = [obj, obj, ..., meta] 8 | // obj = [group, group, ...] 9 | // group = {originalv, v, vt, indexv, indexvt, indices, s} 10 | // meta = {minX, maxX, minY, maxY, minZ, maxZ, size, v, vn, vt} 11 | 12 | // How to use it with WebGL: 13 | // - Unindexed buffers: v and vt can be rendered using drawArrays. (v can be replaced with originalv to disable centering) 14 | // - indexed buffers: indexv, indexvt and indices can be rendered using drawElements (indexv can be replaced with indexoriginalv) 15 | // - vt and indexvt can be empty if the model isn't textured. 16 | 17 | parseOBJ = async (file) => { 18 | 19 | // Temp vars 20 | var 21 | v = [], // all the vertices 22 | vt = [], // all the texture coordinates 23 | //vn = [], // all the normals 24 | vi = [], // indexed vertices for current group 25 | vti = [], // indexed texture coordinates for current group 26 | //vni = [], // normals indices for current group 27 | //mtl = [], // materials 28 | indices = [], // indices (vertices and texture coordinates combined) 29 | indexDict = [], // dictionnary 30 | indexv = [], // vertices index for current group 31 | indexvt = [], // texture coordinates index for current group 32 | //currentvn = [], // current group normals 33 | obj = [], // output 34 | currentobj = {groups: []}, 35 | currentgroup = {}, 36 | //currentmtl = {}, 37 | currents = 1, 38 | currentusemtl, 39 | //objfolder, 40 | //mtlfolder, 41 | //objlines, 42 | //mtllines, 43 | //objline, 44 | //mtlline, 45 | objcommand, 46 | //mtlcommand, 47 | objparam, 48 | //mtlparam, 49 | objlist, 50 | //mtllist, 51 | //normal, 52 | //file, 53 | tmp, i, j, 54 | A, B, C, 55 | AB, BC, 56 | minX = 9e9, minY = 9e9, minZ = 9e9, 57 | maxX = -9e9, maxY = -9e9, maxZ = -9e9, 58 | 59 | // This function is called when a group is complete 60 | // It saves the data buffers (v, originalv, vt / indexv, indexoriginalv, indexvt, indices) and smoothing (s) for the current group 61 | // The group is then added to the current object and a new one is created 62 | // todo: save ucurrentusemtl too 63 | endGroup = x => { 64 | 65 | //console.log("save group"); 66 | //console.log(v, vt, vi); 67 | 68 | currentgroup.originalv = []; 69 | currentgroup.v = []; 70 | currentgroup.vt = []; 71 | //currentgroup.vn = []; 72 | currentgroup.indices = []; 73 | indexDict = []; 74 | currentgroup.indexv = []; 75 | currentgroup.indexoriginalv = []; 76 | currentgroup.indexvt = []; 77 | //currentgroup.rgb = []; 78 | //currentvn = []; 79 | currentDict = []; 80 | lastIndex = 0; 81 | 82 | // For every vertex triplet 83 | for(i = 0; i < vi.length; i += 3){ 84 | 85 | // Retrieve vertices 86 | A = [v[vi[i+0] * 3], v[vi[i+0] * 3 + 1], v[vi[i+0] * 3 + 2]]; 87 | B = [v[vi[i+1] * 3], v[vi[i+1] * 3 + 1], v[vi[i+1] * 3 + 2]]; 88 | C = [v[vi[i+2] * 3], v[vi[i+2] * 3 + 1], v[vi[i+2] * 3 + 2]]; 89 | 90 | // If the model is textured, create a single index for both vertices and texture coordinates 91 | if(vti.length){ 92 | 93 | // Vertex 1 94 | currentDict = vi[i+0] + "/" + vti[i+0]; 95 | 96 | // Add current dict entry into dictionary if not already present 97 | // And push corresponding items in indexv and indexvt 98 | if((t = indexDict.indexOf(currentDict)) == -1){ 99 | indexDict.push(currentDict); 100 | currentgroup.indexoriginalv.push(...A); 101 | currentgroup.indexv.push(A[0]-minX, A[1]-minY, A[2]-minZ); 102 | currentgroup.indexvt.push(vt[vti[i+0] * 2], vt[vti[i+0] * 2 + 1]); 103 | } 104 | 105 | // Add an entry 106 | t = indexDict.indexOf(currentDict); 107 | currentgroup.indices.push(t); 108 | 109 | // Vertex 2 110 | currentDict = vi[i+1] + "/" + vti[i+1]; 111 | 112 | // Add current dict entry into dictionary if not already present 113 | // And push corresponding items in indexv and indexvt 114 | if((t = indexDict.indexOf(currentDict)) == -1){ 115 | indexDict.push(currentDict); 116 | currentgroup.indexoriginalv.push(...B); 117 | currentgroup.indexv.push(B[0]-minX, B[1]-minY, B[2]-minZ); 118 | currentgroup.indexvt.push(vt[vti[i+1] * 2], vt[vti[i+1] * 2 + 1]); 119 | } 120 | 121 | // Add an entry 122 | t = indexDict.indexOf(currentDict); 123 | currentgroup.indices.push(t); 124 | 125 | // Vertex 3 126 | currentDict = vi[i+2] + "/" + vti[i+2]; 127 | 128 | // Add current dict entry into dictionary if not already present 129 | // And push corresponding items in indexv and indexvt 130 | if((t = indexDict.indexOf(currentDict)) == -1){ 131 | indexDict.push(currentDict); 132 | currentgroup.indexoriginalv.push(...C); 133 | currentgroup.indexv.push(C[0]-minX, C[1]-minY, C[2]-minZ); 134 | currentgroup.indexvt.push(vt[vti[i+2] * 2], vt[vti[i+2] * 2 + 1]); 135 | } 136 | 137 | // Add an entry 138 | t = indexDict.indexOf(currentDict); 139 | currentgroup.indices.push(t); 140 | } 141 | 142 | // Fill vertices buffer 143 | 144 | // Original 145 | currentgroup.originalv.push(...A); 146 | currentgroup.originalv.push(...B); 147 | currentgroup.originalv.push(...C); 148 | 149 | // Centered 150 | currentgroup.v.push(A[0] - minX, A[1] - minY, A[2] - minZ); 151 | currentgroup.v.push(B[0] - minX, B[1] - minY, B[2] - minZ); 152 | currentgroup.v.push(C[0] - minX, C[1] - minY, C[2] - minZ); 153 | 154 | // Fill textures coordinates buffer (if applicable) 155 | //console.log(1, vt); 156 | if(vti.length){ 157 | currentgroup.vt.push(vt[vti[i+0] * 2], vt[vti[i+0] * 2 + 1]); 158 | currentgroup.vt.push(vt[vti[i+1] * 2], vt[vti[i+1] * 2 + 1]); 159 | currentgroup.vt.push(vt[vti[i+2] * 2], vt[vti[i+2] * 2 + 1]); 160 | } 161 | } 162 | 163 | 164 | // If the model is not textured, the indexed vertices are the same as in the obj file 165 | if(!vti.length) { 166 | currentgroup.indices = vi; 167 | currentgroup.indexoriginalv = v; 168 | for(i = 0; i < v.length; i+= 3){ 169 | currentgroup.indexv.push(v[i] - minX, v[i + 1] - minY, v[i + 2] - minZ); 170 | } 171 | } 172 | 173 | // Save group smoothness 174 | currentgroup.s = currents; 175 | 176 | // Save indices lists 177 | //currentgroup.vi = vi; 178 | //currentgroup.vti = vti; 179 | //currentgroup.vni = vni; 180 | 181 | // Add group in current object 182 | currentobj.groups.push(currentgroup); 183 | 184 | // Reset group 185 | currentgroup = {}; 186 | 187 | //console.log({indexDict}); 188 | 189 | 190 | // Reset indices arrays 191 | vi = []; 192 | vti = []; 193 | }; 194 | 195 | 196 | //console.log(file); 197 | 198 | // Parse the file 199 | 200 | // Remove comments and line breaks 201 | objlines = file.replace(/#.*\n*/g,'').split(/ *[\r\n]+/); 202 | 203 | // For each line 204 | for(objline of objlines){ 205 | 206 | // Separate command and param(s) 207 | [objcommand, objparam] = objline.split(/ (.*)/); 208 | 209 | // Split params as a list if possible 210 | if(objparam){ 211 | objlist = objparam.split(/ +/); 212 | } 213 | 214 | // Interpret each command 215 | switch(objcommand){ 216 | 217 | 218 | // Set material 219 | case 'usemtl': 220 | 221 | //console.log("mtl", objparam); 222 | 223 | // If the current group is not empty and already has a material different than this one, save it and create a new one 224 | if(vi.length && currentgroup.usemtl != objparam){ 225 | endGroup(); 226 | } 227 | 228 | // Save the material name for the current and next groups 229 | currentusemtl = objparam; 230 | 231 | break; 232 | 233 | // New object 234 | case 'o': 235 | 236 | //console.log("o"); 237 | 238 | // Save current group and current object and create new ones (if current object is not empty) 239 | if(currentobj.groups.length){ 240 | endGroup(); 241 | obj.push(currentobj); 242 | currentobj = {}; 243 | } 244 | 245 | // Save current object's name 246 | currentobj.name = objparam; 247 | 248 | // Initialize groups 249 | currentobj.groups = []; 250 | 251 | // Reset smoothness to 1 252 | currents = 1; 253 | 254 | break; 255 | 256 | // New group 257 | case 'g': 258 | 259 | //console.log("g"); 260 | 261 | // Save current group and create a new one (if it's not empty) 262 | if(vi.length){ 263 | endGroup() 264 | } 265 | 266 | // Save current group's name 267 | currentgroup.name = objparam; 268 | 269 | // Reset smoothness to 1 270 | currents = 1; 271 | 272 | break; 273 | 274 | // Vertex (x, y, z, w*, r*, g*, b*) 275 | case 'v': 276 | 277 | //console.log("v"); 278 | 279 | // Push x, y, z into v 280 | v.push(+objlist[0], +objlist[1], +objlist[2]); 281 | 282 | // Update global min/max coords 283 | minX = Math.min(minX, objlist[0]); 284 | minY = Math.min(minY, objlist[1]); 285 | minZ = Math.min(minZ, objlist[2]); 286 | maxX = Math.max(maxX, objlist[0]); 287 | maxY = Math.max(maxY, objlist[1]); 288 | maxZ = Math.max(maxZ, objlist[2]); 289 | break; 290 | 291 | // Texture coordinates (u, v, w*) 292 | case 'vt': 293 | 294 | //console.log("vt"); 295 | 296 | // Push u, v into vt (w ignored) 297 | vt.push(+objlist[0], +objlist[1]); 298 | break; 299 | 300 | // Normal (ignored) 301 | case 'vn': 302 | break; 303 | 304 | // New face (polygon) 305 | // Polygons with 4+ faces are converted into consecutive triangles 306 | case 'f': 307 | 308 | //console.log("f"); 309 | 310 | // For all possible triangles 311 | for(i = 1; i < objlist.length - 1; i++){ 312 | 313 | // Consider the current triangle 314 | tmp = [objlist[0], objlist[i], objlist[i+1]]; 315 | 316 | // For each summit of the triangle 317 | // Possible formats: 318 | // - "vertex" indices 319 | // - "vertex/texture" indices 320 | // - "vertex/texture/normal" indices 321 | // - "vertex//normal" indices 322 | tmp.map(x => { 323 | 324 | // Split vertex/texture/normal 325 | // indices are decremented because obj files start counting at 1, not 0 326 | x = x.split("/"); 327 | vi.push(+x[0] - 1); 328 | if(x[1]){ 329 | vti.push(+x[1] - 1); 330 | } 331 | // Normals are ignored 332 | //if(x[2]){ 333 | // vni.push(+x[2] - 1); 334 | //} 335 | }); 336 | } 337 | break; 338 | 339 | // Set smoothness for the following faces of the current group 340 | case 's': 341 | 342 | // Get the new smoothness value 343 | tmp = (objparam == 0 || objparam == 'off') ? 0 : 1; 344 | 345 | // Save current group and create a new one (if it's not empty and if the smoothness has changed) 346 | if(vi.length && currents != tmp){ 347 | endGroup(); 348 | } 349 | 350 | // Set smoothness for the current group 351 | currents = tmp; 352 | break; 353 | } 354 | } 355 | 356 | // At the end of the file, push the last group in the current object and the last object in obj 357 | endGroup(); 358 | obj.push(currentobj); 359 | obj.push({v, vt, vi, vti, maxX, maxY, maxZ, minX, minY, minZ, size: Math.max(maxX-minX, maxY-minY, maxZ-minZ)}); 360 | return obj; 361 | } -------------------------------------------------------------------------------- /obj2js/yoshi.mtl: -------------------------------------------------------------------------------- 1 | # Blender3D MTL File: mario_yoshi.blend 2 | # Material Count: 1 3 | newmtl yoshi_green 4 | Ns 96.078431 5 | Ka 0.000000 0.000000 0.000000 6 | Kd 0.800000 0.800000 0.800000 7 | Ks 0.000000 0.000000 0.000000 8 | Ni 1.000000 9 | d 1.000000 10 | illum 0 11 | map_Kd ./yoshi.png 12 | 13 | 14 | -------------------------------------------------------------------------------- /obj2js/yoshi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/obj2js/yoshi.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "w", 3 | "version": "1.0.1", 4 | "description": "A micro WebGL2 framework", 5 | "main": "w.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "github.com/xem/W" 12 | }, 13 | "keywords": [ 14 | "WebGL2", 15 | "WebGL", 16 | "3D" 17 | ], 18 | "author": "Xem", 19 | "license": "SEE LICENSE IN README.md" 20 | } 21 | -------------------------------------------------------------------------------- /w.min.full.js: -------------------------------------------------------------------------------- 1 | debug=0,W={models:{},reset:e=>{W.canvas=e,W.objs=0,W.current={},W.next={},W.textures={},W.gl=e.getContext("webgl2"),W.gl.blendFunc(770,771),W.gl.activeTexture(33984),W.program=W.gl.createProgram(),W.gl.enable(2884),W.gl.shaderSource(t=W.gl.createShader(35633),"#version 300 es\nprecision highp float;in vec4 pos,col,uv,normal;uniform mat4 pv,eye,m,im;uniform vec4 bb;out vec4 v_pos,v_col,v_uv,v_normal;void main(){gl_Position=pv*(v_pos=bb.z>0.?m[3]+eye*(pos*bb):m*pos);v_col=col;v_uv=uv;v_normal=transpose(inverse(m))*normal;}"),W.gl.compileShader(t),W.gl.attachShader(W.program,t),W.gl.shaderSource(t=W.gl.createShader(35632),"#version 300 es\nprecision highp float;in vec4 v_pos,v_col,v_uv,v_normal;uniform vec3 light;uniform vec4 o;uniform sampler2D sampler;out vec4 c;void main(){c=mix(texture(sampler,v_uv.xy),v_col,o[3]);if(o[1]>0.){c=vec4(c.rgb*(dot(light,-normalize(o[0]>0.?vec3(v_normal.xyz):cross(dFdx(v_pos.xyz),dFdy(v_pos.xyz))))+o[2]),c.a);}}"),W.gl.compileShader(t),W.gl.attachShader(W.program,t),W.gl.linkProgram(W.program),W.gl.useProgram(W.program),W.gl.clearColor(1,1,1,1),W.clearColor=e=>W.gl.clearColor(...W.col(e)),W.clearColor("fff"),W.gl.enable(2929),W.light({y:-1}),W.camera({fov:30}),setTimeout(W.draw,16)},setState:(e,t,r,o,a=[],n,l,i,s,m,g,d,c)=>{e.n||="o"+W.objs++,e.size&&(e.w=e.h=e.d=e.size),e.t&&e.t.width&&!W.textures[e.t.id]&&(r=W.gl.createTexture(),W.gl.pixelStorei(37441,!0),W.gl.bindTexture(3553,r),W.gl.pixelStorei(37440,1),W.gl.texImage2D(3553,0,6408,6408,5121,e.t),W.gl.generateMipmap(3553),W.textures[e.t.id]=r),e.fov&&(W.projection=new DOMMatrix([1/Math.tan(e.fov*Math.PI/180)/(W.canvas.width/W.canvas.height),0,0,0,0,1/Math.tan(e.fov*Math.PI/180),0,0,0,0,-1001/999,-1,0,0,-2002/999,0])),e={type:t,...W.current[e.n]=W.next[e.n]||{w:1,h:1,d:1,x:0,y:0,z:0,rx:0,ry:0,rz:0,b:"888",mode:4,mix:0},...e,f:0},W.models[e.type]?.vertices&&!W.models?.[e.type].verticesBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].verticesBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].vertices),35044),!W.models[e.type].normals&&W.smooth&&W.smooth(e),W.models[e.type].normals&&(W.gl.bindBuffer(34962,W.models[e.type].normalsBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].normals.flat()),35044))),W.models[e.type]?.uv&&!W.models[e.type].uvBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].uvBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].uv),35044)),W.models[e.type]?.indices&&!W.models[e.type].indicesBuffer&&(W.gl.bindBuffer(34963,W.models[e.type].indicesBuffer=W.gl.createBuffer()),W.gl.bufferData(34963,new Uint16Array(W.models[e.type].indices),35044)),e.t?e.t&&!e.mix&&(e.mix=0):e.mix=1,W.next[e.n]=e},draw:(e,t,r,o,a=[])=>{for(o in t=e-W.lastFrame,W.lastFrame=e,requestAnimationFrame(W.draw),W.next.camera.g&&W.render(W.next[W.next.camera.g],t,1),r=W.animation("camera"),W.next?.camera?.g&&r.preMultiplySelf(W.next[W.next.camera.g].M||W.next[W.next.camera.g].m),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"eye"),!1,r.toFloat32Array()),r.invertSelf(),r.preMultiplySelf(W.projection),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"pv"),!1,r.toFloat32Array()),W.gl.clear(16640),W.next)W.next[o].t||1!=W.col(W.next[o].b)[3]?a.push(W.next[o]):W.render(W.next[o],t);for(o of(a.sort(((e,t)=>W.dist(t)-W.dist(e))),W.gl.enable(3042),a))["plane","billboard"].includes(o.type)&&W.gl.depthMask(0),W.render(o,t),W.gl.depthMask(1);W.gl.disable(3042),W.gl.uniform3f(W.gl.getUniformLocation(W.program,"light"),W.lerp("light","x"),W.lerp("light","y"),W.lerp("light","z"))},render:(e,t,r=["camera","light","group"].includes(e.type),o)=>{e.t&&(W.gl.bindTexture(3553,W.textures[e.t.id]),W.gl.uniform1i(W.gl.getUniformLocation(W.program,"sampler"),0)),e.fe.a&&(e.f=e.a),W.next[e.n].m=W.animation(e.n),W.next[e.g]&&W.next[e.n].m.preMultiplySelf(W.next[e.g].M||W.next[e.g].m),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"m"),!1,(W.next[e.n].M||W.next[e.n].m).toFloat32Array()),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"im"),!1,new DOMMatrix(W.next[e.n].M||W.next[e.n].m).invertSelf().toFloat32Array()),r||(W.gl.bindBuffer(34962,W.models[e.type].verticesBuffer),W.gl.vertexAttribPointer(o=W.gl.getAttribLocation(W.program,"pos"),3,5126,!1,0,0),W.gl.enableVertexAttribArray(o),W.models[e.type].uvBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].uvBuffer),W.gl.vertexAttribPointer(o=W.gl.getAttribLocation(W.program,"uv"),2,5126,!1,0,0),W.gl.enableVertexAttribArray(o)),(e.s||W.models[e.type].customNormals)&&W.models[e.type].normalsBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].normalsBuffer),W.gl.vertexAttribPointer(o=W.gl.getAttribLocation(W.program,"normal"),3,5126,!1,0,0),W.gl.enableVertexAttribArray(o)),W.gl.uniform4f(W.gl.getUniformLocation(W.program,"o"),e.s,(e.mode>3||W.gl[e.mode]>3)&&!e.ns?1:0,W.ambientLight||.2,e.mix),W.gl.uniform4f(W.gl.getUniformLocation(W.program,"bb"),e.w,e.h,"billboard"==e.type,0),W.models[e.type].indicesBuffer&&W.gl.bindBuffer(34963,W.models[e.type].indicesBuffer),W.gl.vertexAttrib4fv(W.gl.getAttribLocation(W.program,"col"),W.col(e.b)),W.models[e.type].indicesBuffer?W.gl.drawElements(+e.mode||W.gl[e.mode],W.models[e.type].indices.length,5123,0):W.gl.drawArrays(+e.mode||W.gl[e.mode],0,W.models[e.type].vertices.length/3))},lerp:(e,t)=>W.next[e]?.a?W.current[e][t]+(W.next[e][t]-W.current[e][t])*(W.next[e].f/W.next[e].a):W.next[e][t],animation:(e,t=new DOMMatrix)=>W.next[e]?t.translateSelf(W.lerp(e,"x"),W.lerp(e,"y"),W.lerp(e,"z")).rotateSelf(W.lerp(e,"rx"),W.lerp(e,"ry"),W.lerp(e,"rz")).scaleSelf(W.lerp(e,"w"),W.lerp(e,"h"),W.lerp(e,"d")):t,dist:(e,t=W.next.camera)=>e?.m&&t?.m?(t.m.m41-e.m.m41)**2+(t.m.m42-e.m.m42)**2+(t.m.m43-e.m.m43)**2:0,ambient:e=>W.ambientLight=e,col:e=>[...e.replace("#","").match(e.length<5?/./g:/../g).map((t=>("0x"+t)/(e.length<5?15:255))),1],add:(e,t)=>{W.models[e]=t,t.normals&&(W.models[e].customNormals=1),W[e]=t=>W.setState(t,e)},group:e=>W.setState(e,"group"),move:(e,t)=>setTimeout((()=>{W.setState(e)}),t||1),delete:(e,t)=>setTimeout((()=>{delete W.next[e]}),t||1),camera:(e,t)=>setTimeout((()=>{W.setState(e,e.n="camera")}),t||1),light:(e,t)=>t?setTimeout((()=>{W.setState(e,e.n="light")}),t):W.setState(e,e.n="light")},W.smooth=(e,t={},r=[],o,a,n,l,i,s,m,g,d,c,p)=>{for(W.models[e.type].normals=[],n=0;nl?[0,0,0]:[AB[1]*BC[2]-AB[2]*BC[1],AB[2]*BC[0]-AB[0]*BC[2],AB[0]*BC[1]-AB[1]*BC[0]],t[i[0]+"_"+i[1]+"_"+i[2]]||=[0,0,0],t[s[0]+"_"+s[1]+"_"+s[2]]||=[0,0,0],t[m[0]+"_"+m[1]+"_"+m[2]]||=[0,0,0],W.models[e.type].normals[g]=t[i[0]+"_"+i[1]+"_"+i[2]]=t[i[0]+"_"+i[1]+"_"+i[2]].map(((e,t)=>e+p[t])),W.models[e.type].normals[d]=t[s[0]+"_"+s[1]+"_"+s[2]]=t[s[0]+"_"+s[1]+"_"+s[2]].map(((e,t)=>e+p[t])),W.models[e.type].normals[c]=t[m[0]+"_"+m[1]+"_"+m[2]]=t[m[0]+"_"+m[1]+"_"+m[2]].map(((e,t)=>e+p[t]))},W.add("plane",{vertices:[.5,.5,0,-.5,.5,0,-.5,-.5,0,.5,.5,0,-.5,-.5,0,.5,-.5,0],uv:[1,1,0,1,0,0,1,1,0,0,1,0]}),W.add("billboard",W.models.plane),W.add("cube",{vertices:[.5,.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,-.5,.5,.5,.5,-.5,.5,.5,.5,.5,-.5,.5,.5,.5,-.5,.5,-.5,.5,.5,-.5,-.5,.5,.5,-.5,-.5,.5,-.5,-.5,.5,.5,.5,.5,-.5,-.5,.5,.5,.5,.5,.5,-.5,.5,.5,-.5,.5,-.5,-.5,-.5,-.5,-.5,.5,.5,-.5,-.5,-.5,-.5,-.5,.5,-.5,.5,-.5,.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,-.5],uv:[1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0]}),W.cube=e=>W.setState(e,"cube"),W.add("pyramid",{vertices:[-.5,-.5,.5,.5,-.5,.5,0,.5,0,.5,-.5,.5,.5,-.5,-.5,0,.5,0,.5,-.5,-.5,-.5,-.5,-.5,0,.5,0,-.5,-.5,-.5,-.5,-.5,.5,0,.5,0,.5,-.5,.5,-.5,-.5,.5,-.5,-.5,-.5,.5,-.5,.5,-.5,-.5,-.5,.5,-.5,-.5],uv:[0,0,1,0,.5,1,0,0,1,0,.5,1,0,0,1,0,.5,1,0,0,1,0,.5,1,1,1,0,1,0,0,1,1,0,0,1,0]}),((e,t,r,o,a,n,l=[],i=[],s=[],m=20)=>{for(r=0;r<=m;r++)for(o=r*Math.PI/m,e=0;e<=m;e++)t=2*e*Math.PI/m,l.push(+(Math.sin(t)*Math.sin(o)/2).toFixed(6),+(Math.cos(o)/2).toFixed(6),+(Math.cos(t)*Math.sin(o)/2).toFixed(6)),s.push(3.5*Math.sin(e/m),-Math.sin(r/m)),e{W.canvas=e,W.objs=0,W.current={},W.next={},W.textures={},W.gl=e.getContext("webgl2"),W.gl.blendFunc(770,771),W.gl.activeTexture(33984),W.program=W.gl.createProgram(),W.gl.enable(2884),W.gl.shaderSource(t=W.gl.createShader(35633),"#version 300 es\nprecision highp float;in vec4 pos,col,uv,normal;uniform mat4 pv,eye,m,im;uniform vec4 bb;out vec4 v_pos,v_col,v_uv,v_normal;void main(){gl_Position=pv*(v_pos=bb.z>0.?m[3]+eye*(pos*bb):m*pos);v_col=col;v_uv=uv;v_normal=transpose(inverse(m))*normal;}"),W.gl.compileShader(t),W.gl.attachShader(W.program,t),W.gl.shaderSource(t=W.gl.createShader(35632),"#version 300 es\nprecision highp float;in vec4 v_pos,v_col,v_uv,v_normal;uniform vec3 light;uniform vec4 o;uniform sampler2D sampler;out vec4 c;void main(){c=mix(texture(sampler,v_uv.xy),v_col,o[3]);if(o[1]>0.){c=vec4(c.rgb*(dot(light,-normalize(o[0]>0.?vec3(v_normal.xyz):cross(dFdx(v_pos.xyz),dFdy(v_pos.xyz))))+o[2]),c.a);}}"),W.gl.compileShader(t),W.gl.attachShader(W.program,t),W.gl.linkProgram(W.program),W.gl.useProgram(W.program),W.gl.clearColor(1,1,1,1),W.clearColor=e=>W.gl.clearColor(...W.col(e)),W.clearColor("fff"),W.gl.enable(2929),W.light({y:-1}),W.camera({fov:30}),W.draw()},setState:(e,t,r,o,a=[],n,l,i,s,m,d,g,c)=>{e.n||="o"+W.objs++,e.size&&(e.w=e.h=e.d=e.size),e.t&&e.t.width&&!W.textures[e.t.id]&&(r=W.gl.createTexture(),W.gl.pixelStorei(37441,!0),W.gl.bindTexture(3553,r),W.gl.pixelStorei(37440,1),W.gl.texImage2D(3553,0,6408,6408,5121,e.t),W.gl.generateMipmap(3553),W.textures[e.t.id]=r),e.fov&&(W.projection=new DOMMatrix([1/Math.tan(e.fov*Math.PI/180)/(W.canvas.width/W.canvas.height),0,0,0,0,1/Math.tan(e.fov*Math.PI/180),0,0,0,0,-1001/999,-1,0,0,-2e3/999,0])),e={type:t,...W.current[e.n]=W.next[e.n]||{w:1,h:1,d:1,x:0,y:0,z:0,rx:0,ry:0,rz:0,b:"888",mode:4,mix:0},...e,f:0},W.models[e.type]?.vertices&&!W.models?.[e.type].verticesBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].verticesBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].vertices),35044),!W.models[e.type].normals&&W.smooth&&W.smooth(e),W.models[e.type].normals&&(W.gl.bindBuffer(34962,W.models[e.type].normalsBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].normals.flat()),35044))),W.models[e.type]?.uv&&!W.models[e.type].uvBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].uvBuffer=W.gl.createBuffer()),W.gl.bufferData(34962,new Float32Array(W.models[e.type].uv),35044)),W.models[e.type]?.indices&&!W.models[e.type].indicesBuffer&&(W.gl.bindBuffer(34963,W.models[e.type].indicesBuffer=W.gl.createBuffer()),W.gl.bufferData(34963,new Uint16Array(W.models[e.type].indices),35044)),e.t?e.t&&!e.mix&&(e.mix=0):e.mix=1,W.next[e.n]=e},draw:(e,t,r,o,a=[])=>{for(o in t=e-W.lastFrame,W.lastFrame=e,requestAnimationFrame(W.draw),W.gl.clear(16640),r=W.animation("camera"),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"eye"),!1,r.toFloat32Array()),r.invertSelf(),r.preMultiplySelf(W.projection),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"pv"),!1,r.toFloat32Array()),W.gl.uniform3f(W.gl.getUniformLocation(W.program,"light"),W.lerp("light","x"),W.lerp("light","y"),W.lerp("light","z")),W.next)W.next[o].t||1!=W.col(W.next[o].b)[3]?a.push(W.next[o]):W.render(W.next[o],t);for(o in a.sort(((e,t)=>W.dist(t)-W.dist(e))),W.gl.enable(3042),a)W.render(a[o],t);W.gl.disable(3042)},render:(e,t,r)=>{e.t&&(W.gl.bindTexture(3553,W.textures[e.t.id]),W.gl.uniform1i(W.gl.getUniformLocation(W.program,"sampler"),0)),e.fe.a&&(e.f=e.a),W.next[e.n].m=W.animation(e.n),W.next[e.g]&&W.next[e.n].m.preMultiplySelf(W.next[e.g].M||W.next[e.g].m),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"m"),!1,(W.next[e.n].M||W.next[e.n].m).toFloat32Array()),W.gl.uniformMatrix4fv(W.gl.getUniformLocation(W.program,"im"),!1,new DOMMatrix(W.next[e.n].M||W.next[e.n].m).invertSelf().toFloat32Array()),["camera","light","group"].includes(e.type)||(W.gl.bindBuffer(34962,W.models[e.type].verticesBuffer),W.gl.vertexAttribPointer(r=W.gl.getAttribLocation(W.program,"pos"),3,5126,!1,0,0),W.gl.enableVertexAttribArray(r),W.models[e.type].uvBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].uvBuffer),W.gl.vertexAttribPointer(r=W.gl.getAttribLocation(W.program,"uv"),2,5126,!1,0,0),W.gl.enableVertexAttribArray(r)),(e.s||W.models[e.type].customNormals)&&W.models[e.type].normalsBuffer&&(W.gl.bindBuffer(34962,W.models[e.type].normalsBuffer),W.gl.vertexAttribPointer(r=W.gl.getAttribLocation(W.program,"normal"),3,5126,!1,0,0),W.gl.enableVertexAttribArray(r)),W.gl.uniform4f(W.gl.getUniformLocation(W.program,"o"),e.s,(e.mode>3||W.gl[e.mode]>3)&&!e.ns?1:0,W.ambientLight||.2,e.mix),W.gl.uniform4f(W.gl.getUniformLocation(W.program,"bb"),e.w,e.h,"billboard"==e.type,0),W.models[e.type].indicesBuffer&&W.gl.bindBuffer(34963,W.models[e.type].indicesBuffer),W.gl.vertexAttrib4fv(W.gl.getAttribLocation(W.program,"col"),W.col(e.b)),W.models[e.type].indicesBuffer?W.gl.drawElements(+e.mode||W.gl[e.mode],W.models[e.type].indices.length,5123,0):W.gl.drawArrays(+e.mode||W.gl[e.mode],0,W.models[e.type].vertices.length/3))},lerp:(e,t)=>W.next[e]?.a?W.current[e][t]+(W.next[e][t]-W.current[e][t])*(W.next[e].f/W.next[e].a):W.next[e][t],animation:(e,t=new DOMMatrix)=>W.next[e]?t.translateSelf(W.lerp(e,"x"),W.lerp(e,"y"),W.lerp(e,"z")).rotateSelf(W.lerp(e,"rx"),W.lerp(e,"ry"),W.lerp(e,"rz")).scaleSelf(W.lerp(e,"w"),W.lerp(e,"h"),W.lerp(e,"d")):t,dist:(e,t=W.next.camera)=>e?.m&&t?.m?(t.m.m41-e.m.m41)**2+(t.m.m42-e.m.m42)**2+(t.m.m43-e.m.m43)**2:0,ambient:e=>W.ambientLight=e,col:e=>[...e.replace("#","").match(e.length<5?/./g:/../g).map((t=>("0x"+t)/(e.length<5?15:255))),1],add:(e,t)=>{W.models[e]=t,t.normals&&(W.models[e].customNormals=1),W[e]=t=>W.setState(t,e)},group:e=>W.setState(e,"group"),move:(e,t)=>setTimeout((()=>{W.setState(e)}),t||1),delete:(e,t)=>setTimeout((()=>{delete W.next[e]}),t||1),camera:(e,t)=>setTimeout((()=>{W.setState(e,e.n="camera")}),t||1),light:(e,t)=>t?setTimeout((()=>{W.setState(e,e.n="light")}),t):W.setState(e,e.n="light")} -------------------------------------------------------------------------------- /w.min.lite.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xem/W/c5b6fb7d6937d43b1596dded857acade426bd631/w.min.lite.zip --------------------------------------------------------------------------------