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();
218 mWindow.GetRootLayer().SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
220 // Get a handle to the mWindow
221 mWindow.SetBackgroundColor(Color::WHITE);
223 RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
224 renderTask.SetCullMode(false);
227 CreateSceneFromGLTF(mCurrentGlTF);
232 // Respond to a click anywhere on the mWindow
233 mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3DModelExample::OnTouch);
234 mWindow.KeyEventSignal().Connect(this, &Scene3DModelExample::OnKeyEvent);
235 mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelExample::OnWheel);
238 mDoubleTapTime = Timer::New(150);
239 mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelExample::OnDoubleTapTime);
242 bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
244 mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
245 mWheelDelta = std::max(0.5f, mWheelDelta);
246 mWheelDelta = std::min(2.0f, mWheelDelta);
250 mModel.SetProperty(Actor::Property::SCALE, mWheelDelta);
256 bool OnDoubleTapTime()
262 void CreateSceneFromGLTF(uint32_t index)
266 mWindow.GetRootLayer().Remove(mModel);
269 std::string gltfUrl = modeldir;
270 gltfUrl += gltf_list[index];
272 mModel = Dali::Scene3D::Model::New(gltfUrl);
275 mModel.SetProperty(Dali::Actor::Property::SIZE, Vector2(300, 300));
276 mModel.SetProperty(Dali::Actor::Property::POSITION_Y, 100);
280 mModel.SetProperty(Dali::Actor::Property::SIZE, Vector2(600, 600));
282 mModel.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
283 mModel.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
284 mModel.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
287 if(mModel.GetAnimationCount() > 0)
289 Animation animation = (index == 0u) ? mModel.GetAnimation(0u) : mModel.GetAnimation("idleToSquatClip_0");
291 animation.SetLoopCount(0);
295 void SetCameraActor()
297 mCameraActor = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
298 mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
299 mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
309 Vertex skyboxVertices[] = {
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)},
316 {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)},
324 {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)},
332 {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)},
340 {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)},
348 {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)},
356 {Vector3(1.0f, -1.0f, 1.0f)}};
358 const std::string currentVShaderFile(VERTEX_SHADER_URL);
359 const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
361 mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
363 Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
364 .Add("aPosition", Property::VECTOR3));
365 vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
367 mSkyboxGeometry = Geometry::New();
368 mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
369 mSkyboxGeometry.SetType(Geometry::TRIANGLES);
372 Devel::PixelBuffer diffusePixelBuffer = LoadImageFromFile(uri_cube_diffuse_texture);
373 int32_t diffuseFaceSize = diffusePixelBuffer.GetWidth() / 4;
374 Texture texture = Texture::New(TextureType::TEXTURE_CUBE, diffusePixelBuffer.GetPixelFormat(), diffuseFaceSize, diffuseFaceSize);
375 for(int32_t i = 0; i < 6; ++i)
377 UploadTextureFace(texture, diffusePixelBuffer, i);
379 texture.GenerateMipmaps();
381 mSkyboxTextures = TextureSet::New();
382 mSkyboxTextures.SetTexture(0, texture);
384 mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
385 mSkyboxRenderer.SetTextures(mSkyboxTextures);
386 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
388 // Enables the depth test.
389 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
391 // The fragment shader will run only is those pixels that have the max depth value.
392 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
394 mSkyboxActor = Actor::New();
395 mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
396 mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
397 mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
398 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
399 mSkyboxActor.AddRenderer(mSkyboxRenderer);
400 mWindow.Add(mSkyboxActor);
406 * Rotate object Actor along Y axis.
407 * Animation duration is 8 seconds.
408 * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
409 * Each keyframes are interpolated by linear interpolator.
411 mAnimation = Animation::New(8.0);
412 KeyFrames keyframes = KeyFrames::New();
413 float lengthAnimation = 0.25;
414 for(int32_t i = 0; i < 5; ++i)
416 keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
418 mAnimation.AnimateBetween(Property(mModel, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
419 mAnimation.SetLooping(true);
423 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)
425 if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
427 DALI_LOG_ERROR("Can not crop outside of texture area.\n");
430 uint32_t byteSize = bytesPerPixel * xFaceSize * yFaceSize;
431 uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
433 int32_t srcStride = width * bytesPerPixel;
434 int32_t destStride = xFaceSize * bytesPerPixel;
435 int32_t srcOffset = xOffset * bytesPerPixel + yOffset * srcStride;
436 int32_t destOffset = 0;
437 for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
439 memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
440 srcOffset += srcStride;
441 destOffset += destStride;
447 void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
449 uint8_t* imageBuffer = pixelBuffer.GetBuffer();
450 uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
451 uint32_t imageWidth = pixelBuffer.GetWidth();
452 uint32_t imageHeight = pixelBuffer.GetHeight();
454 int32_t faceSize = imageWidth / 4;
456 uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
457 uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
459 uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
462 PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
463 texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
467 void ChangeModel(int32_t direction)
469 mCurrentGlTF += direction;
470 if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
476 mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
478 CreateSceneFromGLTF(mCurrentGlTF);
480 mAnimationStop = false;
485 * This function will change the material Roughness, Metalness or the model orientation when touched
487 bool OnTouch(Actor actor, const TouchEvent& touch)
489 const PointState::Type state = touch.GetState(0);
493 case PointState::DOWN:
499 mDoubleTapTime.Stop();
501 mPointZ = touch.GetScreenPosition(0);
509 case PointState::MOTION:
511 const Size size = mWindow.GetSize();
512 const float scaleX = size.width;
513 const float scaleY = size.height;
514 const Vector2 point = touch.GetScreenPosition(0);
516 // If the touch is not processed above, then change the model orientation
520 * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
521 * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
522 * Final Orientation of camera is combination of Pitch and Yaw quaternion.
523 * For the natural rendering, Skybox also relocated at the new camera position.
526 const float angle1 = ((mPointZ.y - point.y) / scaleY);
527 const float angle2 = ((mPointZ.x - point.x) / scaleX);
529 Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
530 Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
532 Quaternion newModelOrientation = yaw * pitch;
533 mCameraPosition = newModelOrientation.Rotate(mCameraPosition);
534 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
535 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
544 mDoubleTapTime.Start();
562 void OnKeyEvent(const KeyEvent& event)
564 if(event.GetState() == KeyEvent::DOWN)
566 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
570 if(event.GetKeyName() == "Down" ||
571 event.GetKeyName() == "Up")
583 mAnimationStop = !mAnimationStop;
587 if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
592 if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
602 Application& mApplication;
603 CameraActor mCameraActor;
606 Vector3 mCameraPosition;
607 Dali::Scene3D::Model mModel;
610 Quaternion mModelOrientation;
612 Shader mShaderSkybox;
613 Geometry mSkyboxGeometry;
614 TextureSet mSkyboxTextures;
615 Renderer mSkyboxRenderer;
618 Animation mAnimation;
619 bool mProcess{false};
622 Timer mDoubleTapTime;
623 bool mDoubleTap{false};
625 float mWheelDelta{1.0f};
627 int32_t mCurrentGlTF{0};
630 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
632 Application application = Application::New(&argc, &argv);
633 Scene3DModelExample test(application);
634 application.MainLoop();