2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali-toolkit/dali-toolkit.h>
19 #include <dali-toolkit/devel-api/controls/scene3d-view/scene3d-view.h>
20 #include <dali/dali.h>
21 #include <dali/devel-api/adaptor-framework/file-loader.h>
22 #include <dali/devel-api/adaptor-framework/file-stream.h>
23 #include <dali/devel-api/adaptor-framework/image-loading.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/public-api/actors/camera-actor.h>
29 using namespace Dali::Toolkit;
32 * This example shows how to create and display a Scene3dView control.
33 * The application can load 5 different glTF model to Scene3dView control.
34 * Each model has diffirent material. BoomBox shows glossy or matt plastic material.
35 * DamagedHelmet shows a kind of reflective glass and metallic object.
36 * Microphone shows a roughness of metallic objects.
37 * and Lantern shows a realistic difference between wood object and metallic object.
38 * Rotate the camera by swiping.
39 * A double tap changes the model.
46 GLTF_ANIMATED_BOX = 0,
54 const char* gltf_list[6] =
57 * For the BoxAnimated.gltf and its Assets
58 * Donated by Cesium for glTF testing.
59 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoxAnimated
63 * For the Lantern.gltf and its Assets
64 * Donated by Microsoft for glTF testing
65 * Created by Ryan Martin
66 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern
70 * For the BoomBox.gltf and its Assets
71 * Donated by Microsoft for glTF testing
72 * Created by Ryan Martin
73 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox
77 * For the DamagedHelmet.gltf and its Assets
78 * Battle Damaged Sci-fi Helmet - PBR by theblueturtle_, published under a
79 * Creative Commons Attribution-NonCommercial license
80 * https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
84 * For the microphone.gltf and its Assets
85 * Microphone GXL 066 Bafhcteks by Gistold, published under a
86 * Creative Commons Attribution-NonCommercial license
87 * https://sketchfab.com/models/5172dbe9281a45f48cee8c15bdfa1831
91 Vector3 camera_position_list[6] =
93 Vector3(-6.00, -8.00, 12.00),
94 Vector3(-30.0, -40.0, 60.0),
95 Vector3(-0.03, -0.04, 0.06),
96 Vector3(-3.00, -4.00, 6.00),
97 Vector3(-0.00, -3.00, 4.00)};
100 * For the diffuse and specular cube map texture.
101 * These textures are based off version of Wave engine sample
102 * Take from https://github.com/WaveEngine/Samples
104 * Copyright (c) 2016 Wave Coorporation
106 * Permission is hereby granted, free of charge, to any person obtaining a copy
107 * of this software and associated documentation files (the "Software"), to
108 * deal in the Software without restriction, including without limitation the
109 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
110 * sell copies of the Software, and to permit persons to whom the Software is
111 * furnished to do so, subject to the following conditions:
113 * The above copyright notice and this permission notice shall be included in
114 * all copies or substantial portions of the Software.
116 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
117 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
118 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
119 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
120 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
121 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
125 const std::string modeldir = DEMO_MODEL_DIR;
126 const std::string imagedir = DEMO_IMAGE_DIR;
127 const std::string uri_diffuse_texture(imagedir + "forest_diffuse_cubemap.png");
128 const std::string uri_specular_texture(imagedir + "forest_specular_cubemap.png");
130 const int32_t cubeMap_index_x[6] = {2, 0, 1, 1, 1, 3};
131 const int32_t cubeMap_index_y[6] = {1, 1, 0, 2, 1, 1};
133 const char* VERTEX_SHADER_URL = DEMO_SHADER_DIR "cube_shader.vsh";
134 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "cube_shader.fsh";
137 * @brief Load a shader source file
138 * @param[in] The path of the source file
139 * @param[out] The contents of file
140 * @return True if the source was read successfully
142 bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
144 Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
145 FILE* file = fileStream.GetFile();
151 bool retValue = false;
152 if(!fseek(file, 0, SEEK_END))
154 long int size = ftell(file);
157 (!fseek(file, 0, SEEK_SET)))
159 output.resize(size + 1);
160 std::fill(output.begin(), output.end(), 0);
161 ssize_t result = fread(output.data(), size, 1, file);
163 retValue = (result >= 0);
171 * @brief Load vertex and fragment shader source
172 * @param[in] shaderVertexFileName is the filepath of Vertex shader
173 * @param[in] shaderFragFileName is the filepath of Fragment shader
174 * @return the Dali::Shader object
176 Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
179 std::vector<char> bufV, bufF;
181 if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
183 if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
185 shader = Shader::New(bufV.data(), bufF.data());
194 * This example shows how to render glTF model with Scene3dView
196 * - Input UP or DOWN key to make the model rotate or stop.
197 * - Input LEFT or RIGHT key to change glTF model
198 * - Double Touch also changes glTF model.
200 class Scene3dViewController : public ConnectionTracker
203 Scene3dViewController(Application& application)
204 : mApplication(application),
206 mAnimationStop(false)
208 // Connect to the Application's Init signal
209 mApplication.InitSignal().Connect(this, &Scene3dViewController::Create);
212 ~Scene3dViewController()
217 // The Init signal is received once (only) during the Application lifetime
218 void Create(Application& application)
220 mWindow = application.GetWindow();
222 // Get a handle to the mWindow
223 mWindow.SetBackgroundColor(Color::WHITE);
225 RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
226 renderTask.SetCullMode(false);
228 mCurrentGlTF = GLTF_ANIMATED_BOX;
229 CreateSceneFromGLTF(gltf_list[mCurrentGlTF]);
234 // Respond to a click anywhere on the mWindow
235 mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3dViewController::OnTouch);
236 mWindow.KeyEventSignal().Connect(this, &Scene3dViewController::OnKeyEvent);
237 mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3dViewController::OnWheel);
240 mDoubleTapTime = Timer::New(150);
241 mDoubleTapTime.TickSignal().Connect(this, &Scene3dViewController::OnDoubleTapTime);
244 bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
246 mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
247 mWheelDelta = std::max(0.5f, mWheelDelta);
248 mWheelDelta = std::min(2.0f, mWheelDelta);
252 mScene3dView.SetProperty(Actor::Property::SCALE, mWheelDelta);
258 bool OnDoubleTapTime()
264 void CreateSceneFromGLTF(std::string modelName)
268 mWindow.GetRootLayer().Remove(mScene3dView);
271 std::string gltfUrl = modeldir;
272 gltfUrl += modelName;
273 mScene3dView = Scene3dView::New(gltfUrl, uri_diffuse_texture, uri_specular_texture, Vector4::ONE);
275 mScene3dView.SetLight(Scene3dView::LightType::POINT_LIGHT, Vector3(-5, -5, 5), Vector3(1, 1, 1));
277 mWindow.Add(mScene3dView);
278 mScene3dView.PlayAnimations();
281 void SetCameraActor()
283 mCameraPosition = camera_position_list[mCurrentGlTF];
284 mCameraActor = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
285 mCameraActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
286 mCameraActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
287 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
288 mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
289 mCameraActor.SetNearClippingPlane(0.01);
299 Vertex skyboxVertices[] = {
301 {Vector3(-1.0f, 1.0f, -1.0f)},
302 {Vector3(-1.0f, -1.0f, -1.0f)},
303 {Vector3(1.0f, -1.0f, -1.0f)},
304 {Vector3(1.0f, -1.0f, -1.0f)},
305 {Vector3(1.0f, 1.0f, -1.0f)},
306 {Vector3(-1.0f, 1.0f, -1.0f)},
309 {Vector3(-1.0f, -1.0f, 1.0f)},
310 {Vector3(-1.0f, -1.0f, -1.0f)},
311 {Vector3(-1.0f, 1.0f, -1.0f)},
312 {Vector3(-1.0f, 1.0f, -1.0f)},
313 {Vector3(-1.0f, 1.0f, 1.0f)},
314 {Vector3(-1.0f, -1.0f, 1.0f)},
317 {Vector3(1.0f, -1.0f, -1.0f)},
318 {Vector3(1.0f, -1.0f, 1.0f)},
319 {Vector3(1.0f, 1.0f, 1.0f)},
320 {Vector3(1.0f, 1.0f, 1.0f)},
321 {Vector3(1.0f, 1.0f, -1.0f)},
322 {Vector3(1.0f, -1.0f, -1.0f)},
325 {Vector3(-1.0f, -1.0f, 1.0f)},
326 {Vector3(-1.0f, 1.0f, 1.0f)},
327 {Vector3(1.0f, 1.0f, 1.0f)},
328 {Vector3(1.0f, 1.0f, 1.0f)},
329 {Vector3(1.0f, -1.0f, 1.0f)},
330 {Vector3(-1.0f, -1.0f, 1.0f)},
333 {Vector3(-1.0f, 1.0f, -1.0f)},
334 {Vector3(1.0f, 1.0f, -1.0f)},
335 {Vector3(1.0f, 1.0f, 1.0f)},
336 {Vector3(1.0f, 1.0f, 1.0f)},
337 {Vector3(-1.0f, 1.0f, 1.0f)},
338 {Vector3(-1.0f, 1.0f, -1.0f)},
341 {Vector3(-1.0f, -1.0f, -1.0f)},
342 {Vector3(-1.0f, -1.0f, 1.0f)},
343 {Vector3(1.0f, -1.0f, -1.0f)},
344 {Vector3(1.0f, -1.0f, -1.0f)},
345 {Vector3(-1.0f, -1.0f, 1.0f)},
346 {Vector3(1.0f, -1.0f, 1.0f)}};
348 const std::string currentVShaderFile(VERTEX_SHADER_URL);
349 const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
351 mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
353 Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
354 .Add("aPosition", Property::VECTOR3));
355 vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
357 mSkyboxGeometry = Geometry::New();
358 mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
359 mSkyboxGeometry.SetType(Geometry::TRIANGLES);
362 Devel::PixelBuffer diffusePixelBuffer = LoadImageFromFile(uri_diffuse_texture);
363 int32_t diffuseFaceSize = diffusePixelBuffer.GetWidth() / 4;
364 Texture texture = Texture::New(TextureType::TEXTURE_CUBE, diffusePixelBuffer.GetPixelFormat(), diffuseFaceSize, diffuseFaceSize);
365 for(int32_t i = 0; i < 6; ++i)
367 UploadTextureFace(texture, diffusePixelBuffer, i);
369 texture.GenerateMipmaps();
371 mSkyboxTextures = TextureSet::New();
372 mSkyboxTextures.SetTexture(0, texture);
374 mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
375 mSkyboxRenderer.SetTextures(mSkyboxTextures);
376 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
378 // Enables the depth test.
379 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
381 // The fragment shader will run only is those pixels that have the max depth value.
382 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
384 mSkyboxActor = Actor::New();
385 mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
386 mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
387 mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
388 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
389 mSkyboxActor.AddRenderer(mSkyboxRenderer);
390 mWindow.Add(mSkyboxActor);
396 * Rotate object Actor along Y axis.
397 * Animation duration is 8 seconds.
398 * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
399 * Each keyframes are interpolated by linear interpolator.
401 mAnimation = Animation::New(8.0);
402 KeyFrames keyframes = KeyFrames::New();
403 float lengthAnimation = 0.25;
404 for(int32_t i = 0; i < 5; ++i)
406 keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
408 mAnimation.AnimateBetween(Property(mScene3dView, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
409 mAnimation.SetLooping(true);
413 uint8_t* CropBuffer(uint8_t* sourceBuffer, uint32_t bytesPerPixel, uint32_t width, uint32_t height, uint32_t xOffset, uint32_t yOffset, uint32_t xFaceSize, uint32_t yFaceSize)
415 if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
417 DALI_LOG_ERROR("Can not crop outside of texture area.\n");
420 uint32_t byteSize = bytesPerPixel * xFaceSize * yFaceSize;
421 uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
423 int32_t srcStride = width * bytesPerPixel;
424 int32_t destStride = xFaceSize * bytesPerPixel;
425 int32_t srcOffset = xOffset * bytesPerPixel + yOffset * srcStride;
426 int32_t destOffset = 0;
427 for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
429 memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
430 srcOffset += srcStride;
431 destOffset += destStride;
437 void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
439 uint8_t* imageBuffer = pixelBuffer.GetBuffer();
440 uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
441 uint32_t imageWidth = pixelBuffer.GetWidth();
442 uint32_t imageHeight = pixelBuffer.GetHeight();
444 int32_t faceSize = imageWidth / 4;
446 uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
447 uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
449 uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
452 PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
453 texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
457 void ChangeModel(int32_t direction)
459 mCurrentGlTF += direction;
460 if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
466 mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
468 CreateSceneFromGLTF(gltf_list[mCurrentGlTF]);
469 mCameraPosition = camera_position_list[mCurrentGlTF];
470 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
471 if(mCurrentGlTF == GLTF_LANTERN)
473 mCameraActor.SetTargetPosition(Vector3(0.0, -15.0, 0.0));
477 mCameraActor.SetTargetPosition(Vector3::ZERO);
479 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
481 mAnimationStop = false;
486 * This function will change the material Roughness, Metalness or the model orientation when touched
488 bool OnTouch(Actor actor, const TouchEvent& touch)
490 const PointState::Type state = touch.GetState(0);
494 case PointState::DOWN:
500 mDoubleTapTime.Stop();
502 mPointZ = touch.GetScreenPosition(0);
510 case PointState::MOTION:
512 const Size size = mWindow.GetSize();
513 const float scaleX = size.width;
514 const float scaleY = size.height;
515 const Vector2 point = touch.GetScreenPosition(0);
517 // If the touch is not processed above, then change the model orientation
521 * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
522 * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
523 * Final Orientation of camera is combination of Pitch and Yaw quaternion.
524 * For the natural rendering, Skybox also relocated at the new camera position.
527 const float angle1 = ((mPointZ.y - point.y) / scaleY);
528 const float angle2 = ((mPointZ.x - point.x) / scaleX);
530 Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
531 Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
533 Quaternion newModelOrientation = yaw * pitch;
534 mCameraPosition = newModelOrientation.Rotate(mCameraPosition);
535 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
536 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
545 mDoubleTapTime.Start();
563 void OnKeyEvent(const KeyEvent& event)
565 if(event.GetState() == KeyEvent::DOWN)
567 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
571 if(event.GetKeyName() == "Down" ||
572 event.GetKeyName() == "Up")
584 mAnimationStop = !mAnimationStop;
588 if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
593 if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
603 Application& mApplication;
604 CameraActor mCameraActor;
607 Vector3 mCameraPosition;
608 Scene3dView mScene3dView;
611 Quaternion mModelOrientation;
613 Shader mShaderSkybox;
614 Geometry mSkyboxGeometry;
615 TextureSet mSkyboxTextures;
616 Renderer mSkyboxRenderer;
619 Animation mAnimation;
620 bool mProcess{false};
623 Timer mDoubleTapTime;
624 bool mDoubleTap{false};
626 float mWheelDelta{1.0f};
628 int32_t mCurrentGlTF{0};
631 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
633 Application application = Application::New(&argc, &argv);
634 Scene3dViewController test(application);
635 application.MainLoop();