From bc8dfbb1ef88127f61a86d4a8719e33275fb4629 Mon Sep 17 00:00:00 2001 From: Oleksandr Shcherbina Date: Mon, 6 Apr 2015 14:10:28 +0900 Subject: [PATCH] [evas/evas_3D] Billboard mechanism Summary: Add pointer to target billboard node in Evas_3D_Node Skip set flags change orientation for billboard node Add method node_billboard_update to use it for change orientation during traverse by nodes Split API evas_3d_node_look_at_set to have possibility change orientation of node without set flags Reviewers: cedric, Hermet Subscribers: cedric Differential Revision: https://phab.enlightenment.org/D2245 --- src/examples/evas/Makefile.am | 1 + src/examples/evas/evas-3d-shadows.c | 150 ++++++++++++--- .../evas/resources/images/billboard.png | Bin 0 -> 26403 bytes src/lib/evas/Evas_Eo.h | 1 + src/lib/evas/canvas/evas_3d_node.c | 179 ++++++++++++------ src/lib/evas/canvas/evas_3d_node.eo | 29 +++ src/lib/evas/include/evas_private.h | 1 + 7 files changed, 285 insertions(+), 76 deletions(-) create mode 100644 src/examples/evas/resources/images/billboard.png diff --git a/src/examples/evas/Makefile.am b/src/examples/evas/Makefile.am index 0218472979..6a47303011 100644 --- a/src/examples/evas/Makefile.am +++ b/src/examples/evas/Makefile.am @@ -359,6 +359,7 @@ resources/images/four_NM_height.tga \ resources/images/rocks_NM_height.tga \ resources/images/grid.png \ resources/images/grid_n.png \ +resources/images/billboard.png \ resources/models/mesh_for_mmap.eet \ resources/models/mesh_for_mmap.md2 \ resources/models/mesh_for_mmap.obj \ diff --git a/src/examples/evas/evas-3d-shadows.c b/src/examples/evas/evas-3d-shadows.c index c1eb0875d5..701a474067 100644 --- a/src/examples/evas/evas-3d-shadows.c +++ b/src/examples/evas/evas-3d-shadows.c @@ -1,9 +1,11 @@ /** - * This example illustrating use of shadows in the scene and callbacks(clicked, collision). + * This example illustrating use of shadows, callbacks(clicked, collision), + * and technic of the billboard. * Model and cube are clickable. Model detects collision with sphere. * Cube detects collision with sphere, model and cone. * @see evas_3d_scene_shadows_enable_set(Eina_Bool _shadows_enabled) * @see evas_3d_object_callback_register + * @see evas_3d_billboard_set/get * * @verbatim * gcc -o evas-3d-shadows evas-3d-shadows.c evas-3d-primitives.c `pkg-config --libs --cflags efl evas ecore ecore-evas eo eina` -lm @@ -37,7 +39,8 @@ #define SPECULAR_LIGHT 1.0, 1.0, 1.0 static const char *model_path = PACKAGE_EXAMPLES_DIR EVAS_MODEL_FOLDER "/sonic.md2"; - +static const char *image_path = PACKAGE_EXAMPLES_DIR EVAS_IMAGE_FOLDER "/sonic.png"; +static const char *b_image_path = PACKAGE_EXAMPLES_DIR EVAS_IMAGE_FOLDER "/billboard.png"; static const vec2 tex_scale = {1, 1}; static const vec2 fence_tex_scale = {80, 6}; @@ -89,6 +92,7 @@ typedef struct _Body_3D Eo *material; Eo *mesh; Eo *node; + Eo *texture; } Body_3D; typedef struct _Scene_Data @@ -99,6 +103,7 @@ typedef struct _Scene_Data Eo *camera; Eo *light_node; Eo *light; + Eo *mediator; Body_3D sphere; Body_3D cube; @@ -107,6 +112,9 @@ typedef struct _Scene_Data Body_3D model; Body_3D cone; Body_3D fence; + Body_3D billboard; + + Eina_Bool init; } Scene_Data; static void @@ -114,23 +122,37 @@ _show_help() { fprintf(stdout, "Press 'w'/'s' key to move up/down object\n"); fprintf(stdout, "Press 'a'/'d' key to move left/right object\n"); - fprintf(stdout, "Press 'q'/'e' key to to move near/far object\n"); + fprintf(stdout, "Press 'q'/'e' key to move near/far object\n"); fprintf(stdout, "Cude and model can be moved.\n"); fprintf(stdout, "Cube detects intersection with model, sphere, cone\n"); fprintf(stdout, "Model detects intersection with sphere\n"); + fprintf(stdout, "Press '1'/'2' key to change kind of node - billboard/normal model\n"); + fprintf(stdout, "Press Up/Down key to change position of camera\n"); + fprintf(stdout, "Press 'i' key to return initial view of scene\n"); } static Eina_Bool _animate_scene(void *data) { - static int frame = 0; - Body_3D *body = (Body_3D *)data; - - eo_do(body->node, evas_3d_node_mesh_frame_set(body->mesh, frame)); - - /*frame += 32;*/ - - if (frame > 256 * 20) frame = 0; + static float angle = 0; + Evas_Real x, y, z; + Scene_Data *scene = (Scene_Data *)data; + if (scene->init) + { + eo_do(scene->mediator, + evas_3d_node_position_get(EVAS_3D_SPACE_PARENT, &x, &y, &z)); + eo_do(scene->mediator, + evas_3d_node_position_set(sin(angle) * 20 , y , cos(angle) * 20), + evas_3d_node_look_at_set(EVAS_3D_SPACE_PARENT, 0.0, 3.0, 0.0, + EVAS_3D_SPACE_PARENT, 0.0, 5.0, 0.0)); + angle += 0.005; + if (angle > 360) angle = 0.0; + } + else + eo_do(scene->mediator, + evas_3d_node_position_set(0.0, 6.0, 12.0), + evas_3d_node_look_at_set(EVAS_3D_SPACE_PARENT, 0.0, 3.0, 0.0, + EVAS_3D_SPACE_PARENT, 0.0, 5.0, 0.0)); return EINA_TRUE; } @@ -241,6 +263,9 @@ _fence_setup(Body_3D *fence) evas_3d_material_enable_set(EVAS_3D_MATERIAL_SPECULAR, EINA_TRUE), evas_3d_material_enable_set(EVAS_3D_MATERIAL_NORMAL, EINA_TRUE), evas_3d_material_texture_set(EVAS_3D_MATERIAL_NORMAL, texture1), + evas_3d_material_color_set(EVAS_3D_MATERIAL_AMBIENT, 1.0, 1.0, 1.0, 1.0), + evas_3d_material_color_set(EVAS_3D_MATERIAL_DIFFUSE, 1.0, 1.0, 1.0, 1.0), + evas_3d_material_color_set(EVAS_3D_MATERIAL_SPECULAR, 1.0, 1.0, 1.0, 1.0), evas_3d_material_shininess_set(100.0)); fence->mesh = eo_add(EVAS_3D_MESH_CLASS, evas); evas_3d_add_cylinder_frame(fence->mesh, 0, 50, fence_tex_scale); @@ -292,9 +317,9 @@ _box_setup(Body_3D *box) static void _model_setup(Body_3D *model) { - Eo *texture = eo_add(EVAS_3D_TEXTURE_CLASS, evas); - eo_do(texture, - evas_3d_texture_file_set(PACKAGE_EXAMPLES_DIR EVAS_IMAGE_FOLDER "/sonic.png", NULL), + model->texture = eo_add(EVAS_3D_TEXTURE_CLASS, evas); + eo_do(model->texture, + evas_3d_texture_file_set(image_path, NULL), evas_3d_texture_filter_set(EVAS_3D_TEXTURE_FILTER_NEAREST, EVAS_3D_TEXTURE_FILTER_NEAREST), evas_3d_texture_wrap_set(EVAS_3D_WRAP_MODE_REPEAT, @@ -302,8 +327,8 @@ _model_setup(Body_3D *model) model->material = eo_add(EVAS_3D_MATERIAL_CLASS, evas); eo_do(model->material, - evas_3d_material_texture_set(EVAS_3D_MATERIAL_DIFFUSE, texture), - evas_3d_material_texture_set(EVAS_3D_MATERIAL_AMBIENT, texture), + evas_3d_material_texture_set(EVAS_3D_MATERIAL_DIFFUSE, model->texture), + evas_3d_material_texture_set(EVAS_3D_MATERIAL_AMBIENT, model->texture), evas_3d_material_enable_set(EVAS_3D_MATERIAL_AMBIENT, EINA_TRUE), evas_3d_material_enable_set(EVAS_3D_MATERIAL_DIFFUSE, EINA_TRUE), evas_3d_material_enable_set(EVAS_3D_MATERIAL_SPECULAR, EINA_TRUE), @@ -311,7 +336,6 @@ _model_setup(Body_3D *model) model->mesh = eo_add(EVAS_3D_MESH_CLASS, evas); - eo_do(model->mesh, efl_file_set(model_path, NULL), evas_3d_mesh_frame_material_set(0, model->material), @@ -325,25 +349,70 @@ _model_setup(Body_3D *model) evas_3d_node_orientation_angle_axis_set(120.0, -0.577, -0.577, -0.577)); } +static void +_billboard_setup(Scene_Data *data) +{ + data->billboard.texture = eo_add(EVAS_3D_TEXTURE_CLASS, evas); + eo_do(data->billboard.texture, + evas_3d_texture_file_set(b_image_path, NULL), + evas_3d_texture_filter_set(EVAS_3D_TEXTURE_FILTER_NEAREST, + EVAS_3D_TEXTURE_FILTER_NEAREST), + evas_3d_texture_wrap_set(EVAS_3D_WRAP_MODE_REPEAT, + EVAS_3D_WRAP_MODE_REPEAT)); + + data->billboard.mesh = eo_add(EVAS_3D_MESH_CLASS, evas); + evas_3d_add_square_frame(data->billboard.mesh, 0); + + _body_material_set(&(data->billboard), 1.0, 1.0, 1.0); + + eo_do(data->billboard.material, + evas_3d_material_texture_set(EVAS_3D_MATERIAL_DIFFUSE, data->billboard.texture)); + + eo_do(data->billboard.mesh, + evas_3d_mesh_frame_material_set(0, data->billboard.material), + evas_3d_mesh_alpha_func_set(EVAS_3D_COMPARISON_GREATER, 0), + evas_3d_mesh_alpha_test_enable_set(EINA_TRUE), + evas_3d_mesh_shade_mode_set(EVAS_3D_SHADE_MODE_DIFFUSE), + evas_3d_mesh_blending_enable_set(EINA_TRUE), + evas_3d_mesh_blending_func_set(EVAS_3D_BLEND_SRC_ALPHA, + EVAS_3D_BLEND_ONE_MINUS_SRC_ALPHA)); + + data->billboard.node = eo_add(EVAS_3D_NODE_CLASS, evas, + evas_3d_node_constructor(EVAS_3D_NODE_TYPE_MESH)); + eo_do(data->billboard.node, + evas_3d_node_mesh_add(data->billboard.mesh), + evas_3d_node_position_set(0.0, 2.0, 0.0), + evas_3d_node_scale_set(2.2, 4.6, 4.0)); + + eo_do(data->billboard.node, + evas_3d_node_billboard_target_set(data->mediator)); +} + static void _camera_setup(Scene_Data *data) { data->camera = eo_add(EVAS_3D_CAMERA_CLASS, evas); + data->mediator = eo_add(EVAS_3D_NODE_CLASS, evas, + evas_3d_node_constructor(EVAS_3D_NODE_TYPE_NODE)); eo_do(data->camera, - evas_3d_camera_projection_perspective_set(50.0, 1.0, 2.0, 50.0)); + evas_3d_camera_projection_perspective_set(50.0, 1.0, 2.0, 100.0)); data->camera_node = eo_add(EVAS_3D_NODE_CLASS, evas, evas_3d_node_constructor(EVAS_3D_NODE_TYPE_CAMERA)); eo_do(data->camera_node, - evas_3d_node_camera_set(data->camera), + evas_3d_node_camera_set(data->camera)); + + + eo_do(data->mediator, evas_3d_node_position_set(0.0, 6.0, 12.0), evas_3d_node_look_at_set(EVAS_3D_SPACE_PARENT, 0.0, 3.0, 0.0, EVAS_3D_SPACE_PARENT, 0.0, 5.0, 0.0)); - eo_do(data->root_node, evas_3d_node_member_add(data->camera_node)); + eo_do(data->mediator, evas_3d_node_member_add(data->camera_node)); + eo_do(data->root_node, evas_3d_node_member_add(data->mediator)); } static void @@ -362,7 +431,7 @@ _light_setup(Scene_Data *data) evas_3d_node_constructor(EVAS_3D_NODE_TYPE_LIGHT)); eo_do(data->light_node, evas_3d_node_light_set(data->light), - evas_3d_node_position_set(50.0, 50.0, 20.0), + evas_3d_node_position_set(50.0, 50.0, 70.0), evas_3d_node_look_at_set(EVAS_3D_SPACE_PARENT, 15.0, 0.0, -5.0, EVAS_3D_SPACE_PARENT, 0.0, 0.0, 1.0)); eo_do(data->root_node, evas_3d_node_member_add(data->light_node)); @@ -371,6 +440,8 @@ _light_setup(Scene_Data *data) static void _scene_setup(Scene_Data *data) { + data->init = EINA_FALSE; + data->scene = eo_add(EVAS_3D_SCENE_CLASS, evas); eo_do(data->scene, @@ -392,8 +463,9 @@ _scene_setup(Scene_Data *data) _model_setup(&data->model); _cone_setup(&data->cone); _fence_setup(&data->fence); + _billboard_setup(data); - eo_do(data->root_node, + eo_do(data->root_node, evas_3d_node_member_add(data->sphere.node), evas_3d_node_member_add(data->cube.node), evas_3d_node_member_add(data->cylinder.node), @@ -412,6 +484,7 @@ static void _on_key_down(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *eo EINA_UNUSED, void *event_info) { Evas_Event_Key_Down *ev = event_info; + Scene_Data *scene = (Scene_Data *)data; if (!strcmp("w", ev->key)) { Evas_Real x, y, z; @@ -448,6 +521,34 @@ _on_key_down(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *eo EINA_U eo_do(choosed_node, evas_3d_node_position_get(EVAS_3D_SPACE_PARENT, &x, &y, &z)); eo_do(choosed_node, evas_3d_node_position_set(x, y, z + STEP)); } + else if(!strcmp("1", ev->key)) + { + eo_do(scene->root_node, evas_3d_node_member_del(scene->model.node)); + eo_do(scene->root_node, evas_3d_node_member_add(scene->billboard.node)); + } + else if(!strcmp("2", ev->key)) + { + eo_do(scene->root_node, evas_3d_node_member_add(scene->model.node)); + eo_do(scene->root_node, evas_3d_node_member_del(scene->billboard.node)); + } + else if(!strcmp("Up", ev->key)) + { + Evas_Real x, y, z; + eo_do(scene->camera_node, evas_3d_node_position_get(EVAS_3D_SPACE_PARENT, &x, &y, &z)); + eo_do(scene->camera_node, evas_3d_node_position_set(x, y, z + STEP)); + } + else if(!strcmp("Down", ev->key)) + { + Evas_Real x, y, z; + eo_do(scene->camera_node, evas_3d_node_position_get(EVAS_3D_SPACE_PARENT, &x, &y, &z)); + eo_do(scene->camera_node, evas_3d_node_position_set(x, y, z - STEP)); + } + else if (!strcmp("i", ev->key)) + { + scene->init = !scene->init; + eo_do(scene->model.node, evas_3d_node_position_set(0.0, 0.0, 0.0)); + eo_do(scene->billboard.node, evas_3d_node_position_set(0.0, 2.0, 0.0)); + } else { _show_help(); @@ -551,12 +652,15 @@ main(void) eo_do(data.model.node, eo_event_callback_add(EVAS_3D_OBJECT_EVENT_CLICKED, _cb_clicked, NULL)); eo_do(data.model.node, eo_event_callback_add(EVAS_3D_OBJECT_EVENT_COLLISION, _cb_collision, nodes1)); + eo_do(data.billboard.node, eo_event_callback_add(EVAS_3D_OBJECT_EVENT_CLICKED, _cb_clicked, NULL)); + eo_do(data.billboard.node, eo_event_callback_add(EVAS_3D_OBJECT_EVENT_COLLISION, _cb_collision, nodes1)); + evas_object_event_callback_add(image, EVAS_CALLBACK_MOUSE_DOWN, _on_mouse_down, &data); evas_object_event_callback_add(image, EVAS_CALLBACK_KEY_DOWN, _on_key_down, &data); /* Add animator. */ ecore_animator_frametime_set(0.008); - anim = ecore_animator_add(_animate_scene, &data.model); + anim = ecore_animator_add(_animate_scene, &data); /* Enter main loop. */ ecore_main_loop_begin(); diff --git a/src/examples/evas/resources/images/billboard.png b/src/examples/evas/resources/images/billboard.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6915de04f31b651346fd82ed7aa1343288a94e GIT binary patch literal 26403 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 EINA_LIST_FOREACH(pd->members, l, n) { + /*Skip change orientation if node is billboard*/ + Evas_3D_Node_Data *pdm = eo_data_scope_get(n, EVAS_3D_NODE_CLASS); + if (pdm->billboard_target) + continue; eo_do(n, evas_3d_object_change(EVAS_3D_STATE_NODE_PARENT_ORIENTATION, obj)); } if (position) @@ -285,6 +292,46 @@ _node_transform_update(Evas_3D_Node *node, void *data EINA_UNUSED) return EINA_TRUE; } +static Eina_Bool +_node_billboard_update(Evas_3D_Node *node, void *data EINA_UNUSED) +{ + Evas_Vec3 target; + Evas_Vec3 up; + Evas_3D_Node_Data *pd_node = eo_data_scope_get(node, MY_CLASS); + if (pd_node->billboard_target) + { + Evas_3D_Node_Data *pd_target = eo_data_scope_get(pd_node->billboard_target, + MY_CLASS); + evas_vec3_set(&target, pd_target->position.x, pd_target->position.y, + pd_target->position.z); + evas_vec3_set(&up, 0, 1, 0); + + _look_at_set(pd_node, &target, &up); + + if (pd_node->type == EVAS_3D_NODE_TYPE_MESH) + { + evas_mat4_build(&pd_node->data.mesh.matrix_local_to_world, + &pd_node->position, &pd_node->orientation, + &pd_node->scale); + } + else if (pd_node->type == EVAS_3D_NODE_TYPE_LIGHT) + { + evas_mat4_build(&pd_node->data.light.matrix_local_to_world, + &pd_node->position, &pd_node->orientation, + &pd_node->scale); + } + else if (pd_node->type == EVAS_3D_NODE_TYPE_CAMERA) + { + evas_mat4_inverse_build(&pd_node->data.light.matrix_local_to_world, + &pd_node->position, &pd_node->orientation, + &pd_node->scale); + } + else + ERR("Not supported type of node: line %d in file %s", __LINE__ , __FILE__); + } + return EINA_TRUE; +} + static Eina_Bool _node_item_update(Evas_3D_Node *node, void *data EINA_UNUSED) { @@ -589,7 +636,9 @@ _evas_3d_node_evas_3d_object_update_notify(Eo *obj, Evas_3D_Node_Data *pd EINA_U /* Update transform. */ evas_3d_node_tree_traverse(obj, EVAS_3D_TREE_TRAVERSE_LEVEL_ORDER, EINA_FALSE, _node_transform_update, NULL); - + /*Update billboard*/ + evas_3d_node_tree_traverse(obj, EVAS_3D_TREE_TRAVERSE_ANY_ORDER, EINA_FALSE, + _node_billboard_update, NULL); /* Update AABB. */ evas_3d_node_tree_traverse(obj, EVAS_3D_TREE_TRAVERSE_POST_ORDER, EINA_FALSE, _node_aabb_update, NULL); @@ -1035,6 +1084,7 @@ _evas_3d_node_constructor(Eo *obj, Evas_3D_Node_Data *pd, Evas_3D_Node_Type type pd->orientation_inherit = EINA_TRUE; pd->scale_inherit = EINA_TRUE; pd->data.mesh.node_meshes = 0; + pd->billboard_target = NULL; evas_box3_set(&pd->aabb, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); @@ -1310,62 +1360,15 @@ _evas_3d_node_scale_inherit_get(Eo *obj EINA_UNUSED, Evas_3D_Node_Data *pd) return pd->scale_inherit; } -EOLIAN static void -_evas_3d_node_look_at_set(Eo *obj, Evas_3D_Node_Data *pd, - Evas_3D_Space target_space, Evas_Real tx, Evas_Real ty, Evas_Real tz, - Evas_3D_Space up_space, Evas_Real ux, Evas_Real uy, Evas_Real uz) +static void +_look_at_set(Evas_3D_Node_Data *pd, Evas_Vec3 *target, Evas_Vec3 *up) { - Evas_Vec3 target; - Evas_Vec3 up; Evas_Vec3 x, y, z; - /* Target position in parent space. */ - if (target_space == EVAS_3D_SPACE_LOCAL) - { - ERR("TODO:"); - return; - } - else if (target_space == EVAS_3D_SPACE_PARENT) - { - evas_vec3_set(&target, tx, ty, tz); - } - else if (target_space == EVAS_3D_SPACE_WORLD) - { - ERR("TODO:"); - return; - } - else - { - ERR("Invalid coordinate space."); - return; - } - - if (up_space == EVAS_3D_SPACE_LOCAL) - { - evas_vec3_set(&up, ux, uy, uz); - //ERR("TODO:"); - //return; - } - else if (up_space == EVAS_3D_SPACE_PARENT) - { - evas_vec3_set(&up, ux, uy, uz); - } - else if (up_space == EVAS_3D_SPACE_WORLD) - { - ERR("TODO:"); - return; - } - else - { - ERR("Invalid coordinate space."); - return; - } - - /* From now on, everything takes place in parent space. */ - evas_vec3_subtract(&z, &pd->position, &target); + evas_vec3_subtract(&z, &pd->position, target); evas_vec3_normalize(&z, &z); - evas_vec3_cross_product(&x, &up, &z); + evas_vec3_cross_product(&x, up, &z); evas_vec3_normalize(&x, &x); evas_vec3_cross_product(&y, &z, &x); @@ -1416,6 +1419,60 @@ _evas_3d_node_look_at_set(Eo *obj, Evas_3D_Node_Data *pd, pd->orientation.x = (z.x + x.z) * s; pd->orientation.y = (y.z + z.y) * s; } +} + +EOLIAN static void +_evas_3d_node_look_at_set(Eo *obj, Evas_3D_Node_Data *pd, + Evas_3D_Space target_space, Evas_Real tx, Evas_Real ty, Evas_Real tz, + Evas_3D_Space up_space, Evas_Real ux, Evas_Real uy, Evas_Real uz) +{ + Evas_Vec3 target; + Evas_Vec3 up; + + /* Target position in parent space. */ + if (target_space == EVAS_3D_SPACE_LOCAL) + { + ERR("TODO:"); + return; + } + else if (target_space == EVAS_3D_SPACE_PARENT) + { + evas_vec3_set(&target, tx, ty, tz); + } + else if (target_space == EVAS_3D_SPACE_WORLD) + { + ERR("TODO:"); + return; + + } + else + { + ERR("Invalid coordinate space."); + return; + } + + if (up_space == EVAS_3D_SPACE_LOCAL) + { + evas_vec3_set(&up, ux, uy, uz); + //ERR("TODO:"); + //return; + } + else if (up_space == EVAS_3D_SPACE_PARENT) + { + evas_vec3_set(&up, ux, uy, uz); + } + else if (up_space == EVAS_3D_SPACE_WORLD) + { + ERR("TODO:"); + return; + } + else + { + ERR("Invalid coordinate space."); + return; + } + + _look_at_set(pd, &target, &up); eo_do(obj, evas_3d_object_change(EVAS_3D_STATE_NODE_TRANSFORM_ORIENTATION, NULL)); } @@ -1624,4 +1681,20 @@ _evas_3d_node_bounding_sphere_get(Eo *obj EINA_UNUSED, Evas_3D_Node_Data *pd, Ev if (r) *r = pd->bsphere.radius; } +EOLIAN static void +_evas_3d_node_billboard_target_set(Eo *obj EINA_UNUSED, Evas_3D_Node_Data *pd, + Eo *target) +{ + if (pd->billboard_target != target) + { + pd->billboard_target = target; + eo_do(obj, evas_3d_object_change(EVAS_3D_STATE_NODE_PARENT_BILLBOARD, NULL)); + } +} + +EOLIAN static Evas_3D_Node * +_evas_3d_node_billboard_target_get(Eo *obj EINA_UNUSED, Evas_3D_Node_Data *pd) +{ + return pd->billboard_target; +} #include "canvas/evas_3d_node.eo.c" diff --git a/src/lib/evas/canvas/evas_3d_node.eo b/src/lib/evas/canvas/evas_3d_node.eo index b602fd29bc..4ecb26141e 100644 --- a/src/lib/evas/canvas/evas_3d_node.eo +++ b/src/lib/evas/canvas/evas_3d_node.eo @@ -576,6 +576,35 @@ class Evas_3D_Node (Evas_3D_Object, Evas.Common_Interface) int frame; /*@ The animation frame number.*/ } } + + billboard_target { + set { + /* + Set behavior of node like billboard object. + + @param node The given node. + @param billboard Pointer to target node for given node. + + @ingroup Evas_3D_Node + */ + } + get { + /* + Get the target node for billboard object. + + @param node The given node. + @return The pointer to target node for billboard object, + or @c NULL if there're none. + + @see evas_3d_node_billboard_set() + + @ingroup Evas_3D_Node + */ + } + values { + Eo *target; /*@ Target node.*/ + } + } } implements { Eo.Base.destructor; diff --git a/src/lib/evas/include/evas_private.h b/src/lib/evas/include/evas_private.h index c44a240e5e..03df174926 100644 --- a/src/lib/evas/include/evas_private.h +++ b/src/lib/evas/include/evas_private.h @@ -209,6 +209,7 @@ struct _Evas_3D_Node { Eina_List *members; Evas_3D_Node *parent; + Evas_3D_Node *billboard_target; Evas_Vec3 position; Evas_Vec4 orientation;