samples/3D: Added materials sample with reflection, refraction and normals mapping
[sdk] / samples / 3D / materials / materials.ec
1 #ifdef ECERE_STATIC
2 import static "ecere"
3 #else
4 import "ecere"
5 #endif
6
7 class MyApp : GuiApplication
8 {
9    driver = "OpenGL";
10    // driver = "Direct3D";
11    timerResolution = 60;
12
13    bool Cycle(bool idle)
14    {
15       materialsTest.updateModel();
16       return true;
17    }
18 };
19
20 Camera camera
21 {
22    attached,
23    position = Vector3D { 0, 0, -300 },
24    orientation = Euler { }; //30, 30, 0 },
25    fovDirection = vertical,
26    fov = 53;
27 };
28
29 Object cameraTarget { };
30
31 Object posLight
32 {
33    transform = { position = { 0, 0, -10 }, scaling = { 1, 1, 1 } };
34    light =
35    {
36       multiplier = 1.0f;
37       diffuse = white;
38       Kc = 1.0;
39       //Kc = 0.8;
40       Kl = 0.00002;
41       Kq = 0.000002;
42       specular = white; //blue;
43       //flags = { attenuation = true };
44       lightObject = posLight;
45    };
46 };
47
48 Light light
49 {
50    specular = white;
51    multiplier = 1.0f;
52    diffuse = white;
53    orientation = Euler { pitch = 40, yaw = 50 };
54 };
55
56
57 define rGlass = 1.52;
58 define rWater = 1.33;
59 define rIce = 1.309;
60 define rDiamond = 2.42;
61
62 class MaterialsTest : Window
63 {
64    caption = "Materials Test";
65    background = black;
66 #if defined(__EMSCRIPTEN__)
67    anchor = { 0, 0, 0, 0 };
68 #else
69    borderStyle = sizable;
70    hasMaximize = true;
71    hasMinimize = true;
72    hasClose = true;
73    size = { 640, 480 };
74    state = maximized;
75 #endif
76    // glCapabilities.shaders = true;
77
78    Cube cube { };
79    Sphere sphere { };
80    Sphere roundedCylinder { flattenedBody = 0.3f };
81    Object teapot { };
82
83    Object model;
84
85    Euler spin { };
86
87    SkyBox waterBox
88    {
89       size = { 10000, 10000, 10000 },
90       folder = ":watersky", extension = "jpg",
91       cubeMap = waterCubeMap
92    };
93
94    /*
95    SkyBox forestBox
96    {
97       size = { 10000, 10000, 10000 },
98       folder = ":forest", extension = "jpg", newStyle = true,
99       cubeMap = forestCubeMap
100    };
101    */
102    SkyBox sky;
103    font = { "Tahoma", 12, true, outlineSize = 3.0, outlineFade = 0.2 };
104
105    bool updateModel()
106    {
107       static Time lastTime = 0;
108       Time time = GetTime(), diffTime = lastTime ? (time - lastTime) : 0;
109       Transform transform = model.transform;
110       if(spin.yaw || spin.pitch)
111       {
112          int signYaw = 1, signPitch = 1;
113          Radians yaw = spin.yaw, pitch = spin.pitch;
114          Quaternion orientation = transform.orientation;
115          Euler tSpin { yaw * (double)diffTime, pitch * (double)diffTime, 0 };
116          Quaternion thisSpin = tSpin, temp;
117
118          if(yaw < 0) { yaw = -yaw; signYaw = -1; }
119          if(pitch < 0) { pitch = -pitch; signPitch = -1; }
120          yaw   -= (double)diffTime / 3 * yaw;
121          pitch -= (double)diffTime / 3 * pitch;
122          if(yaw < 0.0001) yaw = 0;
123          if(pitch < 0.0001) pitch = 0;
124
125          spin.yaw = yaw * signYaw;
126          spin.pitch = pitch * signPitch;
127
128          temp.Multiply(orientation, thisSpin);
129          orientation.Normalize(temp);
130
131          model.transform.orientation = orientation;
132          //transform.orientation = orientation;
133          //model.transform = transform;
134          model.UpdateTransform();
135          Update(null);
136       }
137       lastTime = time;
138       return true;
139    }
140
141    /*
142    BitmapResource bumpMap { "bumpmap.png", window = this };
143    BitmapResource baseMap { "basetex.bmp", window = this };
144    */
145    BitmapResource bumpMap { ":normal.jpg", mipMaps = true, window = this };
146    // BitmapResource bumpMap { ":normal.png", mipMaps = true, window = this };
147    //BitmapResource baseMap { "diffuse.png", mipMaps = true, window = this };
148    // BitmapResource specularMap { "specular.jpg", mipMaps = true, window = this };
149    CubeMap waterCubeMap { };
150    //CubeMap forestCubeMap { };
151
152    Material material
153    {
154       ambient = blue; //white;
155       diffuse = blue; //white;
156       //specular = { 3.0, 3.0, 3.0 };
157       specular = { 1.0, 1.0, 3.0 };
158       power = 60.0;
159       flags =
160       {
161          doubleSided = true;
162          tile = true;
163          separateSpecular = true;
164       };
165       uScale = 4, vScale = 4;
166       reflectivity = 0.3;
167       refractiveIndex = rGlass;
168       opacity = 0.3;
169    };
170
171    void prepareModel(Object model, float s)
172    {
173       model.transform.scaling = { s, s, s };
174       model.transform.position = { 0, 0, 0 };
175       model.UpdateTransform();
176       model.SetMinMaxRadius(false);
177       model.mesh.ApplyMaterial(material);
178    }
179
180    void viewModel(Object model, double distance, float uvScale)
181    {
182       double r = model.radius * model.transform.scaling.z; //wradius;
183       material.uScale = uvScale;
184       material.vScale = uvScale;
185       camera.position = { 0, 0, -r * distance };
186       cameraTarget.transform.position =
187       {
188          (model.max.x + model.min.x) / 2,
189          (model.max.y + model.min.y) / 2,
190          (model.max.z + model.min.z) / 2
191       };
192       this.model = model;
193       Update(null);
194    }
195
196    void selectEnv(SkyBox sky, CubeMap cubeMap)
197    {
198       material.envMap = cubeMap;
199       this.sky = sky;
200       Update(null);
201    }
202
203    bool OnLoadGraphics()
204    {
205       const String faceNames1[] = { ":watersky/rt", ":watersky/lf",  ":watersky/up", ":watersky/dn", ":watersky/bk", ":watersky/fr" };
206       //const String faceNames2[] = { ":forest/rt", ":forest/lf",  ":forest/up", ":forest/dn", ":forest/bk", ":forest/fr" };
207
208       //material.bumpMap = bumpMap.bitmap;
209       //material.baseMap = baseMap.bitmap;
210       //material.specularMap = specularMap.bitmap;
211       //material.reflectMap = specularMap.bitmap;
212
213       // Water & Sky
214       if(!waterCubeMap.Load(displaySystem, faceNames1, "jpg", true)) waterBox.cubeMap = null;
215       // Forest Cube
216       //if(!forestCubeMap.Load(displaySystem, faceNames2, "jpg", false)) forestBox.cubeMap = null;
217
218       teapot.Load(":teapot.3DS", null, displaySystem);
219       teapot.Merge(displaySystem);
220       teapot.mesh.ApplyMaterial(material);
221       prepareModel(teapot, 1);
222
223       roundedCylinder.Create(displaySystem);
224       prepareModel(roundedCylinder, 50);
225
226       sphere.Create(displaySystem);
227       prepareModel(sphere, 50);
228
229       cube.Create(displaySystem);
230       prepareModel(cube, 50);
231
232       camera.target = cameraTarget;
233       camera.zMin = 1;
234       camera.zMax = 10000;
235
236       waterBox.size = { camera.zMax, camera.zMax, camera.zMax };
237       //forestBox.size = { camera.zMax, camera.zMax, camera.zMax };
238       waterBox.Create(displaySystem);
239       //forestBox.Create(displaySystem);
240
241       viewModel(sphere, 1.5 * sqrt(2), 4.0);
242       //viewModel(cube, 1.5 * 2, 2);
243       //viewModel(teapot, 1.5 * 2, 2);
244       selectEnv(waterBox, waterCubeMap);
245       // selectEnv(forestBox, forestCubeMap);
246       return true;
247    }
248
249    void OnResize(int w, int h)
250    {
251       camera.Setup(w, h, null);
252       camera.Update();
253    }
254
255    void OnRedraw(Surface surface)
256    {
257       int x, y;
258
259       camera.Update();
260       GetMousePosition(&x, &y);
261
262       posLight.transform.position =
263       {
264          (x - clientSize.w / 2) * 400 / clientSize.w,
265          (y - clientSize.h / 2) * 400 / clientSize.h,
266          - 2 * model.wradius
267       };
268       posLight.UpdateTransform();
269
270       surface.Clear(depthBuffer);
271
272       // Setting the light before the camera means the light is in view space
273       //display.SetLight(0, posLight.light);
274       display.SetLight(0, light);
275       // display.ambient = { };
276
277       //display.ambient = { 255, 0, 0 };
278       display.SetCamera(surface, camera);
279
280       // Setting the light before the camera means the light is in world space
281       //display.SetLight(0, light);
282
283       sky.Render(camera, display);
284
285       display.DrawObject(model);
286       display.SetCamera(surface, null);
287
288       surface.outlineColor = black;
289       surface.foreground = white;
290
291       surface.WriteTextf(10, 10,  "('R' to toggle')   Reflection:     %s,   Refraction: %s",
292          material.reflectivity ? "ON" : "OFF",
293          material.refractiveIndex ? "ON" : "OFF");
294       surface.WriteTextf(10, 40,  "('C' to toggle')   Shape:            %s ",
295          model == cube ? "Cube" : model == sphere ? "Sphere" : model == roundedCylinder ? "Rounded Cylinder" : "Teapot");
296       /*surface.WriteTextf(10, 70,  "('E' to toggle')   Environment:  %s",
297          material.envMap == waterCubeMap ? "Water & Sky" : "Forest");*/
298       surface.WriteTextf(10, 100, "('B' to toggle')   Normal Map:   %s",
299          material.bumpMap ? "ON" : "OFF");
300       surface.WriteTextf(10, 130, "Left-Button Drag: Rotate Camera;   Middle-Button Drag: Spin Model;   Right-Button Drag: Rotate Light");
301       surface.WriteTextf(10, 160, "Scroll Wheel: Move camera closer / further");
302 #ifndef __EMSCRIPTEN__
303       surface.WriteTextf(10, 200, "Shaders:     %s", glCapabilities.shaders ? "ON" : "OFF");
304 #endif
305    }
306
307    Point startClick;
308    bool moving, movingCamera, movingLight;
309    Time clickTime;
310    Point startPosition;
311    Euler startOrientation;
312
313    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
314    {
315       clickTime = GetTime();
316       Capture();
317       startClick = { x, y };
318       moving = true;
319       return true;
320    }
321
322    bool OnMiddleButtonUp(int x, int y, Modifiers mods)
323    {
324       if(moving)
325       {
326          ReleaseCapture();
327          moving = false;
328       }
329       return true;
330    }
331
332    bool OnLeftButtonDown(int x, int y, Modifiers mods)
333    {
334       if(!movingCamera && !moving && !movingLight)
335       {
336          startPosition.x = x;
337          startPosition.y = y;
338          startOrientation = camera.eulerOrientation;
339          Capture();
340          movingCamera = true;
341       }
342       return true;
343    }
344
345    bool OnLeftButtonUp(int x, int y, Modifiers mods)
346    {
347       if(movingCamera)
348       {
349          ReleaseCapture();
350          movingCamera = false;
351       }
352       return true;
353    }
354
355    bool OnRightButtonDown(int x, int y, Modifiers mods)
356    {
357       if(!moving && !movingLight)
358       {
359          startPosition.x = x;
360          startPosition.y = y;
361          startOrientation = light.orientation;
362          Capture();
363          movingLight = true;
364       }
365       return true;
366    }
367
368    bool OnRightButtonUp(int x, int y, Modifiers mods)
369    {
370       if(movingLight)
371       {
372          ReleaseCapture();
373          movingLight = false;
374       }
375       return true;
376    }
377
378    bool OnMouseMove(int x, int y, Modifiers mods)
379    {
380       if(moving)
381       {
382          Time time = GetTime(), diffTime = Max(time - clickTime, 0.01);
383          spin.yaw   += Degrees { (x - startClick.x) / (25.0 * (double)diffTime) };
384          spin.pitch += Degrees { (startClick.y - y) / (25.0 * (double)diffTime) };
385          startClick = { x, y };
386          clickTime = time;
387       }
388       else if(movingCamera)
389       {
390          Euler euler
391          {
392             startOrientation.yaw   - (x - startPosition.x),
393             startOrientation.pitch + (y - startPosition.y),
394             0
395          };
396
397          camera.eulerOrientation = euler;
398
399          Update(null);
400       }
401       else if(movingLight)
402       {
403          light.orientation = Euler
404          {
405             startOrientation.yaw   - (x - startPosition.x),
406             startOrientation.pitch + (y - startPosition.y),
407             0
408          };
409
410          Update(null);
411       }
412       Update(null);
413       return true;
414    }
415
416    bool OnKeyHit(Key key, unichar ch)
417    {
418       switch(key)
419       {
420          case wheelDown: case minus: camera.position.z *= 1.1111111f; Update(null); break;
421          case wheelUp: case plus: camera.position.z *= 0.9f; Update(null); break;
422          case f3:
423             glCapabilities.shaders ^= true;
424             Update(null);
425             break;
426          case c:
427             if(model == cube)
428                viewModel(sphere, 1.5 * sqrt(2), 4);
429             else if(model == sphere)
430                viewModel(roundedCylinder, 1.5 * sqrt(2), 4);
431             else if(model == roundedCylinder)
432                viewModel(teapot, 1.5 * sqrt(2), 1.5);
433             else if(model == teapot)
434                viewModel(cube, 1.5 * 2, 2);
435             Update(null);
436             break;
437          /*case e:
438             if(sky == waterBox)
439                selectEnv(forestBox, forestCubeMap);
440             else
441                selectEnv(waterBox, waterCubeMap);
442             break;*/
443          case d:
444          {
445             static bool debugging = false;
446             debugging ^= true;
447             DefaultShader::shader().debugging(debugging);
448             Update(null);
449             break;
450          }
451          case b:
452             material.bumpMap = material.bumpMap ? null : bumpMap.bitmap;
453             Update(null);
454             break;
455          case r:
456             if(material.reflectivity && material.refractiveIndex)
457             {
458                material.refractiveIndex = 0;
459                material.opacity = 1.0;
460                material.reflectivity = 0.8;
461             }
462             else if(material.reflectivity)
463             {
464                material.reflectivity = 0;
465                material.refractiveIndex = rGlass;
466                material.opacity = 0.3;
467             }
468             else if(material.refractiveIndex)
469             {
470                material.refractiveIndex = 0.0;
471                material.opacity = 1.0;
472             }
473             else
474             {
475                material.reflectivity = 0.3;
476                material.refractiveIndex = rGlass;
477                material.opacity = 0.3;
478             }
479             Update(null);
480             break;
481       }
482       return true;
483    }
484 }
485
486 MaterialsTest materialsTest {};