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/dali.h>
20 #include <dali/devel-api/adaptor-framework/file-loader.h>
21 #include <dali/devel-api/adaptor-framework/file-stream.h>
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/actors/camera-actor.h>
27 #include <dali-scene3d/public-api/controls/model/model.h>
30 using namespace Dali::Toolkit;
33 * This example shows how to create and display a Model control.
34 * The application can load 5 different glTF model to Model control.
35 * Each model has diffirent material. BoomBox shows glossy or matt plastic material.
36 * DamagedHelmet shows a kind of reflective glass and metallic object.
37 * Microphone shows a roughness of metallic objects.
38 * and Lantern shows a realistic difference between wood object and metallic object.
39 * Rotate the camera by swiping.
40 * A double tap changes the model.
46 static constexpr int32_t NUM_OF_GLTF_MODELS = 7;
48 const char* gltf_list[7] =
51 * For the BoxAnimated.gltf and its Assets
52 * Donated by Cesium for glTF testing.
53 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoxAnimated
57 * For the Lantern.gltf and its Assets
58 * Donated by Microsoft for glTF testing
59 * Created by Ryan Martin
60 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern
64 * For the BoomBox.gltf and its Assets
65 * Donated by Microsoft for glTF testing
66 * Created by Ryan Martin
67 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox
71 * For the DamagedHelmet.gltf and its Assets
72 * Battle Damaged Sci-fi Helmet - PBR by theblueturtle_, published under a
73 * Creative Commons Attribution-NonCommercial license
74 * https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
78 * For the microphone.gltf and its Assets
79 * Microphone GXL 066 Bafhcteks by Gistold, published under a
80 * Creative Commons Attribution-NonCommercial license
81 * https://sketchfab.com/models/5172dbe9281a45f48cee8c15bdfa1831
85 * For the beer_model.dli and its Assets
86 * This model includes a bottle of beer and cube box.
90 * For the exercise_model.dli and its Assets
91 * This model includes a sportsman
93 "exercise_model.dli"};
96 * For the diffuse and specular cube map texture.
97 * These textures are based off version of Wave engine sample
98 * Take from https://github.com/WaveEngine/Samples
100 * Copyright (c) 2016 Wave Coorporation
102 * Permission is hereby granted, free of charge, to any person obtaining a copy
103 * of this software and associated documentation files (the "Software"), to
104 * deal in the Software without restriction, including without limitation the
105 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
106 * sell copies of the Software, and to permit persons to whom the Software is
107 * furnished to do so, subject to the following conditions:
109 * The above copyright notice and this permission notice shall be included in
110 * all copies or substantial portions of the Software.
112 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
113 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
114 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
115 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
116 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
117 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
121 const std::string modeldir = DEMO_MODEL_DIR;
122 const std::string imagedir = DEMO_IMAGE_DIR;
123 const std::string uri_cube_diffuse_texture(imagedir + "forest_diffuse_cubemap.png");
124 const std::string uri_diffuse_texture(imagedir + "Studio/Irradiance.ktx");
125 const std::string uri_specular_texture(imagedir + "Studio/Radiance.ktx");
127 const int32_t cubeMap_index_x[6] = {2, 0, 1, 1, 1, 3};
128 const int32_t cubeMap_index_y[6] = {1, 1, 0, 2, 1, 1};
130 const char* VERTEX_SHADER_URL = DEMO_SHADER_DIR "cube_shader.vsh";
131 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "cube_shader.fsh";
134 * @brief Load a shader source file
135 * @param[in] The path of the source file
136 * @param[out] The contents of file
137 * @return True if the source was read successfully
139 bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
141 Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
142 FILE* file = fileStream.GetFile();
148 bool retValue = false;
149 if(!fseek(file, 0, SEEK_END))
151 long int size = ftell(file);
154 (!fseek(file, 0, SEEK_SET)))
156 output.resize(size + 1);
157 std::fill(output.begin(), output.end(), 0);
158 ssize_t result = fread(output.data(), size, 1, file);
160 retValue = (result >= 0);
168 * @brief Load vertex and fragment shader source
169 * @param[in] shaderVertexFileName is the filepath of Vertex shader
170 * @param[in] shaderFragFileName is the filepath of Fragment shader
171 * @return the Dali::Shader object
173 Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
176 std::vector<char> bufV, bufF;
178 if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
180 if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
182 shader = Shader::New(bufV.data(), bufF.data());
191 * This example shows how to render glTF model with Model
193 * - Input UP or DOWN key to make the model rotate or stop.
194 * - Input LEFT or RIGHT key to change glTF model
195 * - Double Touch also changes glTF model.
197 class Scene3DModelExample : public ConnectionTracker
200 Scene3DModelExample(Application& application)
201 : mApplication(application),
203 mAnimationStop(false)
205 // Connect to the Application's Init signal
206 mApplication.InitSignal().Connect(this, &Scene3DModelExample::Create);
209 ~Scene3DModelExample()
214 // The Init signal is received once (only) during the Application lifetime
215 void Create(Application& application)
217 mWindow = application.GetWindow();
219 // Get a handle to the mWindow
220 mWindow.SetBackgroundColor(Color::WHITE);
222 RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
223 renderTask.SetCullMode(false);
226 CreateSceneFromGLTF(mCurrentGlTF);
231 // Respond to a click anywhere on the mWindow
232 mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3DModelExample::OnTouch);
233 mWindow.KeyEventSignal().Connect(this, &Scene3DModelExample::OnKeyEvent);
234 mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelExample::OnWheel);
237 mDoubleTapTime = Timer::New(150);
238 mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelExample::OnDoubleTapTime);
241 bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
243 mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
244 mWheelDelta = std::max(0.5f, mWheelDelta);
245 mWheelDelta = std::min(2.0f, mWheelDelta);
249 mModel.SetProperty(Actor::Property::SCALE, mWheelDelta);
255 bool OnDoubleTapTime()
261 void CreateSceneFromGLTF(uint32_t index)
265 mWindow.GetRootLayer().Remove(mModel);
268 std::string gltfUrl = modeldir;
269 gltfUrl += gltf_list[index];
271 mModel = Dali::Scene3D::Model::New(gltfUrl);
274 mModel.SetProperty(Dali::Actor::Property::SIZE, Vector2(300, 300));
275 mModel.SetProperty(Dali::Actor::Property::POSITION_Y, 100);
279 mModel.SetProperty(Dali::Actor::Property::SIZE, Vector2(600, 600));
281 mModel.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
282 mModel.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
283 mModel.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
286 if(mModel.GetAnimationCount() > 0)
288 Animation animation = (index == 0u) ? mModel.GetAnimation(0u) : mModel.GetAnimation("idleToSquatClip_0");
290 animation.SetLoopCount(0);
294 void SetCameraActor()
296 mCameraActor = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
297 mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
298 mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
308 Vertex skyboxVertices[] = {
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)},
315 {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)},
323 {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)},
331 {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)},
339 {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)},
347 {Vector3(-1.0f, 1.0f, -1.0f)},
350 {Vector3(-1.0f, -1.0f, -1.0f)},
351 {Vector3(-1.0f, -1.0f, 1.0f)},
352 {Vector3(1.0f, -1.0f, -1.0f)},
353 {Vector3(1.0f, -1.0f, -1.0f)},
354 {Vector3(-1.0f, -1.0f, 1.0f)},
355 {Vector3(1.0f, -1.0f, 1.0f)}};
357 const std::string currentVShaderFile(VERTEX_SHADER_URL);
358 const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
360 mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
362 Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
363 .Add("aPosition", Property::VECTOR3));
364 vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
366 mSkyboxGeometry = Geometry::New();
367 mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
368 mSkyboxGeometry.SetType(Geometry::TRIANGLES);
371 Devel::PixelBuffer diffusePixelBuffer = LoadImageFromFile(uri_cube_diffuse_texture);
372 int32_t diffuseFaceSize = diffusePixelBuffer.GetWidth() / 4;
373 Texture texture = Texture::New(TextureType::TEXTURE_CUBE, diffusePixelBuffer.GetPixelFormat(), diffuseFaceSize, diffuseFaceSize);
374 for(int32_t i = 0; i < 6; ++i)
376 UploadTextureFace(texture, diffusePixelBuffer, i);
378 texture.GenerateMipmaps();
380 mSkyboxTextures = TextureSet::New();
381 mSkyboxTextures.SetTexture(0, texture);
383 mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
384 mSkyboxRenderer.SetTextures(mSkyboxTextures);
385 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
387 // Enables the depth test.
388 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
390 // The fragment shader will run only is those pixels that have the max depth value.
391 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
393 mSkyboxActor = Actor::New();
394 mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
395 mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
396 mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
397 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
398 mSkyboxActor.AddRenderer(mSkyboxRenderer);
399 mWindow.Add(mSkyboxActor);
405 * Rotate object Actor along Y axis.
406 * Animation duration is 8 seconds.
407 * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
408 * Each keyframes are interpolated by linear interpolator.
410 mAnimation = Animation::New(8.0);
411 KeyFrames keyframes = KeyFrames::New();
412 float lengthAnimation = 0.25;
413 for(int32_t i = 0; i < 5; ++i)
415 keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
417 mAnimation.AnimateBetween(Property(mModel, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
418 mAnimation.SetLooping(true);
422 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)
424 if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
426 DALI_LOG_ERROR("Can not crop outside of texture area.\n");
429 uint32_t byteSize = bytesPerPixel * xFaceSize * yFaceSize;
430 uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
432 int32_t srcStride = width * bytesPerPixel;
433 int32_t destStride = xFaceSize * bytesPerPixel;
434 int32_t srcOffset = xOffset * bytesPerPixel + yOffset * srcStride;
435 int32_t destOffset = 0;
436 for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
438 memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
439 srcOffset += srcStride;
440 destOffset += destStride;
446 void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
448 uint8_t* imageBuffer = pixelBuffer.GetBuffer();
449 uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
450 uint32_t imageWidth = pixelBuffer.GetWidth();
451 uint32_t imageHeight = pixelBuffer.GetHeight();
453 int32_t faceSize = imageWidth / 4;
455 uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
456 uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
458 uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
461 PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
462 texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
466 void ChangeModel(int32_t direction)
468 mCurrentGlTF += direction;
469 if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
475 mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
477 CreateSceneFromGLTF(mCurrentGlTF);
479 mAnimationStop = false;
484 * This function will change the material Roughness, Metalness or the model orientation when touched
486 bool OnTouch(Actor actor, const TouchEvent& touch)
488 const PointState::Type state = touch.GetState(0);
492 case PointState::DOWN:
498 mDoubleTapTime.Stop();
500 mPointZ = touch.GetScreenPosition(0);
508 case PointState::MOTION:
510 const Size size = mWindow.GetSize();
511 const float scaleX = size.width;
512 const float scaleY = size.height;
513 const Vector2 point = touch.GetScreenPosition(0);
515 // If the touch is not processed above, then change the model orientation
519 * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
520 * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
521 * Final Orientation of camera is combination of Pitch and Yaw quaternion.
522 * For the natural rendering, Skybox also relocated at the new camera position.
525 const float angle1 = ((mPointZ.y - point.y) / scaleY);
526 const float angle2 = ((mPointZ.x - point.x) / scaleX);
528 Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
529 Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
531 Quaternion newModelOrientation = yaw * pitch;
532 mCameraPosition = newModelOrientation.Rotate(mCameraPosition);
533 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
534 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
543 mDoubleTapTime.Start();
561 void OnKeyEvent(const KeyEvent& event)
563 if(event.GetState() == KeyEvent::DOWN)
565 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
569 if(event.GetKeyName() == "Down" ||
570 event.GetKeyName() == "Up")
582 mAnimationStop = !mAnimationStop;
586 if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
591 if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
601 Application& mApplication;
602 CameraActor mCameraActor;
605 Vector3 mCameraPosition;
606 Dali::Scene3D::Model mModel;
609 Quaternion mModelOrientation;
611 Shader mShaderSkybox;
612 Geometry mSkyboxGeometry;
613 TextureSet mSkyboxTextures;
614 Renderer mSkyboxRenderer;
617 Animation mAnimation;
618 bool mProcess{false};
621 Timer mDoubleTapTime;
622 bool mDoubleTap{false};
624 float mWheelDelta{1.0f};
626 int32_t mCurrentGlTF{0};
629 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
631 Application application = Application::New(&argc, &argv);
632 Scene3DModelExample test(application);
633 application.MainLoop();