2 * Copyright (c) 2023 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-scene3d/dali-scene3d.h>
19 #include <dali-toolkit/dali-toolkit.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 Model control.
33 * The application can load 5 different glTF model to Model 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 const char* name; ///< The name of the model.
47 const Vector2 size; ///< The size of the model
48 const float yPosition; ///< The position of the model in the Y axis.
51 const ModelInfo gltf_list[] =
54 * For the BoxAnimated.glb
55 * Donated by Cesium for glTF testing.
56 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoxAnimated
58 {"BoxAnimated.glb", Vector2(300.0f, 300.0f), 100.0f},
60 * For the quantized Duck.gltf and its Assets
61 * Created by Sony Computer Entertainment Inc.
62 * Licensed under the SCEA Shared Source License, Version 1.0
63 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Duck/glTF-Quantized
65 {"Duck.gltf", Vector2(600.0f, 600.0f), 300.0f},
67 * For the Lantern.gltf and its Assets
68 * Donated by Microsoft for glTF testing
69 * Created by Ryan Martin
70 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern
72 {"Lantern.gltf", Vector2(600.0f, 600.0f), 0.0f},
74 * For the BoomBox.gltf and its Assets
75 * Donated by Microsoft for glTF testing
76 * Created by Ryan Martin
77 * Take from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/BoomBox
79 {"BoomBox.gltf", Vector2(600.0f, 600.0f), 0.0f},
81 * For the DamagedHelmet.glb
82 * Battle Damaged Sci-fi Helmet - PBR by theblueturtle_, published under a
83 * Creative Commons Attribution-NonCommercial license
84 * https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
86 {"DamagedHelmet.glb", Vector2(600.0f, 600.0f), 0.0f},
88 * For the microphone.gltf and its Assets
89 * Microphone GXL 066 Bafhcteks by Gistold, published under a
90 * Creative Commons Attribution-NonCommercial license
91 * https://sketchfab.com/models/5172dbe9281a45f48cee8c15bdfa1831
93 {"microphone.gltf", Vector2(600.0f, 600.0f), 0.0f},
95 * For the beer_model.dli and its Assets
96 * This model includes a bottle of beer and cube box.
98 {"beer_model.dli", Vector2(600.0f, 600.0f), 0.0f},
100 * For the exercise_model.dli and its Assets
101 * This model includes a sportsman
103 {"exercise_model.dli", Vector2(600.0f, 600.0f), 0.0f},
106 const int32_t NUM_OF_GLTF_MODELS = sizeof(gltf_list) / sizeof(gltf_list[0]);
109 * For the diffuse and specular cube map texture.
110 * These textures are based off version of Wave engine sample
111 * Take from https://github.com/WaveEngine/Samples
113 * Copyright (c) 2016 Wave Coorporation
115 * Permission is hereby granted, free of charge, to any person obtaining a copy
116 * of this software and associated documentation files (the "Software"), to
117 * deal in the Software without restriction, including without limitation the
118 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
119 * sell copies of the Software, and to permit persons to whom the Software is
120 * furnished to do so, subject to the following conditions:
122 * The above copyright notice and this permission notice shall be included in
123 * all copies or substantial portions of the Software.
125 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
126 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
127 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
128 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
129 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
130 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
134 const std::string modeldir = DEMO_MODEL_DIR;
135 const std::string imagedir = DEMO_IMAGE_DIR;
136 const std::string uri_cube_diffuse_texture(imagedir + "forest_diffuse_cubemap.png");
137 const std::string uri_diffuse_texture(imagedir + "Studio/Irradiance.ktx");
138 const std::string uri_specular_texture(imagedir + "Studio/Radiance.ktx");
140 const int32_t cubeMap_index_x[6] = {2, 0, 1, 1, 1, 3};
141 const int32_t cubeMap_index_y[6] = {1, 1, 0, 2, 1, 1};
143 const char* VERTEX_SHADER_URL = DEMO_SHADER_DIR "cube_shader.vsh";
144 const char* FRAGMENT_SHADER_URL = DEMO_SHADER_DIR "cube_shader.fsh";
147 * @brief Load a shader source file
148 * @param[in] The path of the source file
149 * @param[out] The contents of file
150 * @return True if the source was read successfully
152 bool LoadShaderCode(const std::string& fullpath, std::vector<char>& output)
154 Dali::FileStream fileStream(fullpath, FileStream::READ | FileStream::BINARY);
155 FILE* file = fileStream.GetFile();
161 bool retValue = false;
162 if(!fseek(file, 0, SEEK_END))
164 long int size = ftell(file);
167 (!fseek(file, 0, SEEK_SET)))
169 output.resize(size + 1);
170 std::fill(output.begin(), output.end(), 0);
171 ssize_t result = fread(output.data(), size, 1, file);
173 retValue = (result >= 0);
181 * @brief Load vertex and fragment shader source
182 * @param[in] shaderVertexFileName is the filepath of Vertex shader
183 * @param[in] shaderFragFileName is the filepath of Fragment shader
184 * @return the Dali::Shader object
186 Shader LoadShaders(const std::string& shaderVertexFileName, const std::string& shaderFragFileName)
189 std::vector<char> bufV, bufF;
191 if(LoadShaderCode(shaderVertexFileName.c_str(), bufV))
193 if(LoadShaderCode(shaderFragFileName.c_str(), bufF))
195 shader = Shader::New(bufV.data(), bufF.data());
204 * This example shows how to render glTF model with Model
206 * - Input UP or DOWN key to make the model rotate or stop.
207 * - Input LEFT or RIGHT key to change glTF model
208 * - Double Touch also changes glTF model.
210 class Scene3DModelExample : public ConnectionTracker
213 Scene3DModelExample(Application& application)
214 : mApplication(application),
216 mAnimationStop(false)
218 // Connect to the Application's Init signal
219 mApplication.InitSignal().Connect(this, &Scene3DModelExample::Create);
222 ~Scene3DModelExample()
227 // The Init signal is received once (only) during the Application lifetime
228 void Create(Application& application)
230 mWindow = application.GetWindow();
231 mWindow.GetRootLayer().SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
233 // Get a handle to the mWindow
234 mWindow.SetBackgroundColor(Color::WHITE);
236 RenderTask renderTask = mWindow.GetRenderTaskList().GetTask(0);
237 renderTask.SetCullMode(false);
240 CreateSceneFromGLTF(mCurrentGlTF);
245 // Respond to a click anywhere on the mWindow
246 mWindow.GetRootLayer().TouchedSignal().Connect(this, &Scene3DModelExample::OnTouch);
247 mWindow.KeyEventSignal().Connect(this, &Scene3DModelExample::OnKeyEvent);
248 mWindow.GetRootLayer().WheelEventSignal().Connect(this, &Scene3DModelExample::OnWheel);
251 mDoubleTapTime = Timer::New(150);
252 mDoubleTapTime.TickSignal().Connect(this, &Scene3DModelExample::OnDoubleTapTime);
255 bool OnWheel(Actor actor, const WheelEvent& wheelEvent)
257 mWheelDelta -= wheelEvent.GetDelta() * 0.025f;
258 mWheelDelta = std::max(0.5f, mWheelDelta);
259 mWheelDelta = std::min(2.0f, mWheelDelta);
263 mModel.SetProperty(Actor::Property::SCALE, mWheelDelta);
269 bool OnDoubleTapTime()
275 void CreateSceneFromGLTF(uint32_t index)
277 mReadyToLoad = false;
280 mWindow.GetRootLayer().Remove(mModel);
283 std::string gltfUrl = modeldir;
284 gltfUrl += gltf_list[index].name;
286 mModel = Dali::Scene3D::Model::New(gltfUrl);
287 mModel.SetProperty(Dali::Actor::Property::SIZE, gltf_list[index].size);
288 mModel.SetProperty(Dali::Actor::Property::POSITION_Y, gltf_list[index].yPosition);
289 mModel.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
290 mModel.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
291 mModel.SetImageBasedLightSource(uri_diffuse_texture, uri_specular_texture, 0.6f);
293 mModel.ResourceReadySignal().Connect(this, &Scene3DModelExample::ResourceReady);
298 void ResourceReady(Control control)
301 if(mModel.GetAnimationCount() > 0)
303 Animation animation = (std::string("exercise_model.dli") == gltf_list[mCurrentGlTF].name) ? mModel.GetAnimation("idleToSquatClip_0") : mModel.GetAnimation(0u);
305 animation.SetLoopCount(0);
309 void SetCameraActor()
311 mCameraActor = mWindow.GetRenderTaskList().GetTask(0).GetCameraActor();
312 mCameraPosition = mCameraActor.GetProperty<Vector3>(Dali::Actor::Property::POSITION);
313 mCameraActor.SetType(Dali::Camera::LOOK_AT_TARGET);
323 Vertex skyboxVertices[] = {
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)},
349 {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)},
357 {Vector3(-1.0f, 1.0f, -1.0f)},
358 {Vector3(1.0f, 1.0f, -1.0f)},
359 {Vector3(1.0f, 1.0f, 1.0f)},
360 {Vector3(1.0f, 1.0f, 1.0f)},
361 {Vector3(-1.0f, 1.0f, 1.0f)},
362 {Vector3(-1.0f, 1.0f, -1.0f)},
365 {Vector3(-1.0f, -1.0f, -1.0f)},
366 {Vector3(-1.0f, -1.0f, 1.0f)},
367 {Vector3(1.0f, -1.0f, -1.0f)},
368 {Vector3(1.0f, -1.0f, -1.0f)},
369 {Vector3(-1.0f, -1.0f, 1.0f)},
370 {Vector3(1.0f, -1.0f, 1.0f)}};
372 const std::string currentVShaderFile(VERTEX_SHADER_URL);
373 const std::string currentFShaderFile(FRAGMENT_SHADER_URL);
375 mShaderSkybox = LoadShaders(currentVShaderFile, currentFShaderFile);
377 Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map()
378 .Add("aPosition", Property::VECTOR3));
379 vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
381 mSkyboxGeometry = Geometry::New();
382 mSkyboxGeometry.AddVertexBuffer(vertexBuffer);
383 mSkyboxGeometry.SetType(Geometry::TRIANGLES);
385 Dali::Scene3D::Loader::EnvironmentMapData environmentMapData;
386 Dali::Scene3D::Loader::LoadEnvironmentMap(uri_cube_diffuse_texture, environmentMapData);
387 Texture texture = environmentMapData.GetTexture();
389 mSkyboxTextures = TextureSet::New();
390 mSkyboxTextures.SetTexture(0, texture);
392 mSkyboxRenderer = Renderer::New(mSkyboxGeometry, mShaderSkybox);
393 mSkyboxRenderer.SetTextures(mSkyboxTextures);
394 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
396 // Enables the depth test.
397 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
399 // The fragment shader will run only is those pixels that have the max depth value.
400 mSkyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
402 mSkyboxActor = Actor::New();
403 mSkyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
404 mSkyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
405 mSkyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
406 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
407 mSkyboxActor.AddRenderer(mSkyboxRenderer);
408 mWindow.Add(mSkyboxActor);
414 * Rotate object Actor along Y axis.
415 * Animation duration is 8 seconds.
416 * Five keyframes are set for each 90 degree. i.e., 0, 90, 180, 270, and 360.
417 * Each keyframes are interpolated by linear interpolator.
419 mAnimation = Animation::New(8.0);
420 KeyFrames keyframes = KeyFrames::New();
421 float lengthAnimation = 0.25;
422 for(int32_t i = 0; i < 5; ++i)
424 keyframes.Add(i * lengthAnimation, Quaternion(Degree(i * 90.0), Vector3::YAXIS));
426 mAnimation.AnimateBetween(Property(mModel, Dali::Actor::Property::ORIENTATION), keyframes, Animation::Interpolation::LINEAR);
427 mAnimation.SetLooping(true);
431 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)
433 if(xOffset + xFaceSize > width || yOffset + yFaceSize > height)
435 DALI_LOG_ERROR("Can not crop outside of texture area.\n");
438 uint32_t byteSize = bytesPerPixel * xFaceSize * yFaceSize;
439 uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
441 int32_t srcStride = width * bytesPerPixel;
442 int32_t destStride = xFaceSize * bytesPerPixel;
443 int32_t srcOffset = xOffset * bytesPerPixel + yOffset * srcStride;
444 int32_t destOffset = 0;
445 for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
447 memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
448 srcOffset += srcStride;
449 destOffset += destStride;
455 void UploadTextureFace(Texture& texture, Devel::PixelBuffer pixelBuffer, int32_t faceIndex)
457 uint8_t* imageBuffer = pixelBuffer.GetBuffer();
458 uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelBuffer.GetPixelFormat());
459 uint32_t imageWidth = pixelBuffer.GetWidth();
460 uint32_t imageHeight = pixelBuffer.GetHeight();
462 int32_t faceSize = imageWidth / 4;
464 uint32_t xOffset = cubeMap_index_x[faceIndex] * faceSize;
465 uint32_t yOffset = cubeMap_index_y[faceIndex] * faceSize;
467 uint8_t* tempImageBuffer = CropBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, faceSize, faceSize);
470 PixelData pixelData = PixelData::New(tempImageBuffer, faceSize * faceSize * bytesPerPixel, faceSize, faceSize, pixelBuffer.GetPixelFormat(), PixelData::FREE);
471 texture.Upload(pixelData, CubeMapLayer::POSITIVE_X + faceIndex, 0, 0, 0, faceSize, faceSize);
475 void ChangeModel(int32_t direction)
482 mCurrentGlTF += direction;
483 if(mCurrentGlTF >= NUM_OF_GLTF_MODELS)
489 mCurrentGlTF = NUM_OF_GLTF_MODELS - 1;
491 CreateSceneFromGLTF(mCurrentGlTF);
493 mAnimationStop = false;
498 * This function will change the material Roughness, Metalness or the model orientation when touched
500 bool OnTouch(Actor actor, const TouchEvent& touch)
502 const PointState::Type state = touch.GetState(0);
506 case PointState::DOWN:
512 mDoubleTapTime.Stop();
514 mPointZ = touch.GetScreenPosition(0);
522 case PointState::MOTION:
524 const Size size = mWindow.GetSize();
525 const float scaleX = size.width;
526 const float scaleY = size.height;
527 const Vector2 point = touch.GetScreenPosition(0);
529 // If the touch is not processed above, then change the model orientation
533 * This is the motion for the swipe to rotate camera that targeting Vector3::ZERO.
534 * Each quaternion is used to rotate camera by Pitch and Yaw respectively.
535 * Final Orientation of camera is combination of Pitch and Yaw quaternion.
536 * For the natural rendering, Skybox also relocated at the new camera position.
539 const float angle1 = ((mPointZ.y - point.y) / scaleY);
540 const float angle2 = ((mPointZ.x - point.x) / scaleX);
542 Quaternion pitch(Radian(Degree(angle1 * -200.0f)), (mCameraPosition.z >= 0) ? Vector3::XAXIS : -Vector3::XAXIS);
543 Quaternion yaw(Radian(Degree(angle2 * -200.0f)), -Vector3::YAXIS);
545 Quaternion newModelOrientation = yaw * pitch;
546 mCameraPosition = newModelOrientation.Rotate(mCameraPosition);
547 mCameraActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
548 mSkyboxActor.SetProperty(Dali::Actor::Property::POSITION, mCameraPosition);
557 mDoubleTapTime.Start();
575 void OnKeyEvent(const KeyEvent& event)
577 if(event.GetState() == KeyEvent::DOWN)
579 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
583 if(event.GetKeyName() == "Down" ||
584 event.GetKeyName() == "Up")
596 mAnimationStop = !mAnimationStop;
600 if(IsKey(event, Dali::DALI_KEY_CURSOR_RIGHT))
605 if(IsKey(event, Dali::DALI_KEY_CURSOR_LEFT))
615 Application& mApplication;
616 CameraActor mCameraActor;
619 Vector3 mCameraPosition;
620 Dali::Scene3D::Model mModel;
623 Quaternion mModelOrientation;
625 Shader mShaderSkybox;
626 Geometry mSkyboxGeometry;
627 TextureSet mSkyboxTextures;
628 Renderer mSkyboxRenderer;
631 Animation mAnimation;
632 bool mProcess{false};
635 Timer mDoubleTapTime;
636 bool mDoubleTap{false};
638 float mWheelDelta{1.0f};
640 int32_t mCurrentGlTF{0};
642 bool mReadyToLoad{true};
645 int32_t DALI_EXPORT_API main(int32_t argc, char** argv)
647 Application application = Application::New(&argc, &argv);
648 Scene3DModelExample test(application);
649 application.MainLoop();