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-view/model-view.h>
30 using namespace Dali::Toolkit;
33 * This example shows how to create and display a ModelView control.
34 * The application can load 5 different glTF model to ModelView 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_modelView.dli and its Assets
86 * This model includes a bottle of beer and cube box.
90 * For the exercise_modelView.dli and its Assets
91 * This model includes a sportsman
93 "exercise_modelView.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 ModelView
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 Scene3DModelViewExample : public ConnectionTracker
200 Scene3DModelViewExample(Application& application)
201 : mApplication(application),
203 mAnimationStop(false)
205 // Connect to the Application's Init signal
206 mApplication.InitSignal().Connect(this, &Scene3DModelViewExample::Create);
209 ~Scene3DModelViewExample()
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, &Scene3DModelViewExample::OnTouch);
233 mWindow.KeyEventSignal().Connect(this, &Scene3DModelViewExample::OnKeyEvent);
234 mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelViewExample::OnWheel);
237 mDoubleTapTime = Timer::New(150);
238 mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelViewExample::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 mModelView.SetProperty(Actor::Property::SCALE, mWheelDelta);
255 bool OnDoubleTapTime()
261 void CreateSceneFromGLTF(uint32_t index)
265 mWindow.GetRootLayer().Remove(mModelView);
268 std::string gltfUrl = modeldir;
269 gltfUrl += gltf_list[index];
271 mModelView = Dali::Scene3D::ModelView::New(gltfUrl);
274 mModelView.SetProperty(Dali::Actor::Property::SIZE, Vector2(300, 300));
275 mModelView.SetProperty(Dali::Actor::Property::POSITION_Y, 100);
279 mModelView.SetProperty(Dali::Actor::Property::SIZE, Vector2(600, 600));
281 mModelView.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
282 mModelView.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
283 mModelView.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
284 mModelView.FitSize(true);
285 mModelView.FitCenter(true);
287 mWindow.Add(mModelView);
288 if(mModelView.GetAnimationCount()>0)
290 Animation animation = (index == 0u) ? mModelView.GetAnimation(0u) : mModelView.GetAnimation("idleToSquatClip_0");
292 animation.SetLoopCount(0);
296 void SetCameraActor()
298 mCameraActor = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
299 mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
300 mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
310 Vertex skyboxVertices[] = {
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)},
317 {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)},
325 {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)},
333 {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)},
341 {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)},
349 {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)},
357 {Vector3(1.0f, -1.0f, 1.0f)}};
359 const std::string currentVShaderFile(VERTEX_SHADER_URL);
360 const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
362 mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
364 Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
365 .Add("aPosition", Property::VECTOR3));
366 vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
368 mSkyboxGeometry = Geometry::New();
369 mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
370 mSkyboxGeometry.SetType(Geometry::TRIANGLES);
373 Devel::PixelBuffer diffusePixelBuffer = LoadImageFromFile(uri_cube_diffuse_texture);
374 int32_t diffuseFaceSize = diffusePixelBuffer.GetWidth() / 4;
375 Texture texture = Texture::New(TextureType::TEXTURE_CUBE, diffusePixelBuffer.GetPixelFormat(), diffuseFaceSize, diffuseFaceSize);
376 for(int32_t i = 0; i < 6; ++i)
378 UploadTextureFace(texture, diffusePixelBuffer, i);
380 texture.GenerateMipmaps();
382 mSkyboxTextures = TextureSet::New();
383 mSkyboxTextures.SetTexture(0, texture);
385 mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
386 mSkyboxRenderer.SetTextures(mSkyboxTextures);
387 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
389 // Enables the depth test.
390 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
392 // The fragment shader will run only is those pixels that have the max depth value.
393 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
395 mSkyboxActor = Actor::New();
396 mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
397 mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
398 mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
399 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
400 mSkyboxActor.AddRenderer(mSkyboxRenderer);
401 mWindow.Add(mSkyboxActor);
407 * Rotate object Actor along Y axis.
408 * Animation duration is 8 seconds.
409 * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
410 * Each keyframes are interpolated by linear interpolator.
412 mAnimation = Animation::New(8.0);
413 KeyFrames keyframes = KeyFrames::New();
414 float lengthAnimation = 0.25;
415 for(int32_t i = 0; i < 5; ++i)
417 keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
419 mAnimation.AnimateBetween(Property(mModelView, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
420 mAnimation.SetLooping(true);
424 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)
426 if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
428 DALI_LOG_ERROR("Can not crop outside of texture area.\n");
431 uint32_t byteSize = bytesPerPixel * xFaceSize * yFaceSize;
432 uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
434 int32_t srcStride = width * bytesPerPixel;
435 int32_t destStride = xFaceSize * bytesPerPixel;
436 int32_t srcOffset = xOffset * bytesPerPixel + yOffset * srcStride;
437 int32_t destOffset = 0;
438 for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
440 memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
441 srcOffset += srcStride;
442 destOffset += destStride;
448 void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
450 uint8_t* imageBuffer = pixelBuffer.GetBuffer();
451 uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
452 uint32_t imageWidth = pixelBuffer.GetWidth();
453 uint32_t imageHeight = pixelBuffer.GetHeight();
455 int32_t faceSize = imageWidth / 4;
457 uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
458 uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
460 uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
463 PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
464 texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
468 void ChangeModel(int32_t direction)
470 mCurrentGlTF += direction;
471 if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
477 mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
479 CreateSceneFromGLTF(mCurrentGlTF);
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 Dali::Scene3D::ModelView mModelView;
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 Scene3DModelViewExample test(application);
635 application.MainLoop();