| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Graph sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/internal/scenegraph_dev.h> | |
| /*svg proto*/ | |
| #include <gpac/scenegraph_svg.h> | |
| /*MPEG4 tags (for internal nodes)*/ | |
| #include <gpac/nodes_mpeg4.h> | |
| /*X3D tags (for internal nodes)*/ | |
| #include <gpac/nodes_x3d.h> | |
| #include <gpac/events.h> | |
| #include <gpac/nodes_svg.h> | |
| #ifdef GPAC_HAS_QJS | |
| #include "qjs_common.h" | |
| #endif | |
| static void ReplaceDEFNode(GF_Node *FromNode, GF_Node *node, GF_Node *newNode, Bool updateOrderedGroup); | |
| #ifndef GPAC_DISABLE_SVG | |
| static void ReplaceIRINode(GF_Node *FromNode, GF_Node *oldNode, GF_Node *newNode); | |
| #endif | |
| static void node_modif_stub(GF_SceneGraph *sg, GF_Node *node, GF_FieldInfo *info, GF_Node *script) | |
| { | |
| } | |
| GF_EXPORT | |
| GF_SceneGraph *gf_sg_new() | |
| { | |
| GF_SceneGraph *tmp; | |
| GF_SAFEALLOC(tmp, GF_SceneGraph); | |
| if (!tmp) return NULL; | |
| tmp->exported_nodes = gf_list_new(); | |
| #ifndef GPAC_DISABLE_VRML | |
| tmp->protos = gf_list_new(); | |
| tmp->unregistered_protos = gf_list_new(); | |
| tmp->Routes = gf_list_new(); | |
| tmp->routes_to_activate = gf_list_new(); | |
| tmp->routes_to_destroy = gf_list_new(); | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| tmp->dom_evt_mx = gf_mx_new("DOMEvent"); | |
| tmp->dom_evt = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_DOCUMENT, tmp); | |
| tmp->xlink_hrefs = gf_list_new(); | |
| tmp->smil_timed_elements = gf_list_new(); | |
| tmp->modified_smil_timed_elements = gf_list_new(); | |
| tmp->listeners_to_add = gf_list_new(); | |
| #endif | |
| #ifdef GPAC_HAS_QJS | |
| tmp->scripts = gf_list_new(); | |
| tmp->objects = gf_list_new(); | |
| #endif | |
| tmp->on_node_modified = node_modif_stub; | |
| //test some functions only used in weird smil anim or dom JS, and not addressed in test suite | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| node_modif_stub(NULL, NULL, NULL, NULL); | |
| gf_node_remove_id(NULL); | |
| gf_node_list_get_child(NULL, 0); | |
| gf_node_get_parent_count(NULL); | |
| gf_node_get_num_instances(NULL); | |
| gf_node_get_log_name(NULL); | |
| gf_sg_get_namespace(NULL, 0); | |
| gf_node_parent_of(NULL, NULL); | |
| gf_sg_get_parent(tmp); | |
| gf_node_get_attribute_count(NULL); | |
| gf_sg_xml_node_clone(NULL, NULL, NULL, NULL, 0); | |
| gf_dom_flatten_textContent(NULL); | |
| gf_smil_timing_pause(NULL); | |
| gf_smil_timing_resume(NULL); | |
| gf_smil_get_media_duration(NULL); | |
| } | |
| #endif | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| GF_SceneGraph *gf_sg_new_subscene(GF_SceneGraph *scene) | |
| { | |
| GF_SceneGraph *tmp; | |
| if (!scene) return NULL; | |
| tmp = gf_sg_new(); | |
| if (!tmp) return NULL; | |
| tmp->parent_scene = scene; | |
| tmp->script_action = scene->script_action; | |
| tmp->script_action_cbck = scene->script_action_cbck; | |
| tmp->script_load = scene->script_load; | |
| tmp->on_node_modified = scene->on_node_modified; | |
| /*by default use the same callbacks*/ | |
| tmp->userpriv = scene->userpriv; | |
| tmp->GetSceneTime = scene->GetSceneTime; | |
| tmp->NodeCallback = scene->NodeCallback; | |
| #ifndef GPAC_DISABLE_VRML | |
| tmp->GetExternProtoLib = scene->GetExternProtoLib; | |
| #endif | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| void gf_sg_set_node_callback(GF_SceneGraph *sg, gf_sg_node_init_callback NodeCallback) | |
| { | |
| sg->NodeCallback = NodeCallback; | |
| } | |
| GF_EXPORT | |
| void gf_sg_set_scene_time_callback(GF_SceneGraph *sg, Double (*GetSceneTime)(void *user_priv)) | |
| { | |
| sg->GetSceneTime = GetSceneTime; | |
| } | |
| GF_EXPORT | |
| Double gf_node_get_scene_time(GF_Node *node) | |
| { | |
| if (!node || !node->sgprivate->scenegraph->GetSceneTime) return 0.0; | |
| return node->sgprivate->scenegraph->GetSceneTime(node->sgprivate->scenegraph->userpriv); | |
| } | |
| GF_EXPORT | |
| void gf_sg_del(GF_SceneGraph *sg) | |
| { | |
| if (!sg) return; | |
| if (sg->destroy_cookie) | |
| *sg->destroy_cookie = GF_TRUE; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (sg->global_qp) { | |
| gf_node_unregister(sg->global_qp, NULL); | |
| sg->global_qp = NULL; | |
| } | |
| #endif | |
| gf_sg_reset(sg); | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_dom_event_target_del(sg->dom_evt); | |
| gf_list_del(sg->xlink_hrefs); | |
| gf_list_del(sg->smil_timed_elements); | |
| gf_list_del(sg->modified_smil_timed_elements); | |
| gf_list_del(sg->listeners_to_add); | |
| gf_mx_del(sg->dom_evt_mx); | |
| #endif | |
| #ifdef GPAC_HAS_QJS | |
| gf_list_del(sg->scripts); | |
| sg->scripts = NULL; | |
| gf_list_del(sg->objects); | |
| sg->objects = NULL; | |
| #ifndef GPAC_DISABLE_SVG | |
| if (sg->svg_js) { | |
| void gf_svg_script_context_del(void *svg_js, GF_SceneGraph *scenegraph); | |
| gf_svg_script_context_del(sg->svg_js, sg); | |
| } | |
| #endif | |
| #endif //GPAC_HAS_QJS | |
| #ifndef GPAC_DISABLE_VRML | |
| gf_list_del(sg->Routes); | |
| gf_list_del(sg->protos); | |
| gf_list_del(sg->unregistered_protos); | |
| gf_list_del(sg->routes_to_activate); | |
| gf_list_del(sg->routes_to_destroy); | |
| #endif | |
| gf_list_del(sg->exported_nodes); | |
| if (sg->referencing_commands) { | |
| u32 i, cnt = gf_list_count(sg->referencing_commands); | |
| for (i = 0; i < cnt; i++) { | |
| GF_SceneGraph **in_scene_ptr = (GF_SceneGraph **)gf_list_get(sg->referencing_commands, i); | |
| *in_scene_ptr = NULL; | |
| } | |
| gf_list_del(sg->referencing_commands); | |
| sg->referencing_commands = NULL; | |
| } | |
| gf_free(sg); | |
| } | |
| //unused, cf gf_sg_reset | |
| #if 0 | |
| /*recursive traverse of the whole graph to check for scope mixes (nodes from an inline graph | |
| inserted in a parent graph through bind or routes). We must do this otherwise we're certain to get random | |
| crashes or mem leaks.*/ | |
| void SG_GraphRemoved(GF_Node *node, GF_SceneGraph *sg) | |
| { | |
| u32 i, count; | |
| GF_FieldInfo info; | |
| u32 tag; | |
| tag = node->sgprivate->tag; | |
| if (tag == TAG_ProtoNode) return; | |
| /*not possible in DOM ?*/ | |
| if (tag>GF_NODE_RANGE_LAST_VRML) return; | |
| count = gf_node_get_field_count(node); | |
| for (i=0; i<count; i++) { | |
| gf_node_get_field(node, i, &info); | |
| if (info.fieldType==GF_SG_VRML_SFNODE) { | |
| GF_Node *n = *(GF_Node **) info.far_ptr; | |
| if (n) { | |
| if (n->sgprivate->scenegraph==sg) { | |
| /*if root of graph, skip*/ | |
| if (sg->RootNode!=n) { | |
| gf_node_unregister(n, node); | |
| /*don't forget to remove node...*/ | |
| *(GF_Node **) info.far_ptr = NULL; | |
| } | |
| } else { | |
| SG_GraphRemoved(n, sg); | |
| } | |
| } | |
| } | |
| else if (info.fieldType==GF_SG_VRML_MFNODE) { | |
| GF_ChildNodeItem *cur, *prev, *list = *(GF_ChildNodeItem **) info.far_ptr; | |
| prev = NULL; | |
| while (list) { | |
| if (list->node->sgprivate->scenegraph==sg) { | |
| gf_node_unregister(list->node, node); | |
| if (prev) prev->next = list->next; | |
| else *(GF_ChildNodeItem **) info.far_ptr = list->next; | |
| cur = list; | |
| gf_free(cur); | |
| } else { | |
| SG_GraphRemoved(list->node, sg); | |
| } | |
| list = list->next; | |
| } | |
| } | |
| /*for uncompressed files (bt/xmt/laserML), also reset command lists*/ | |
| else if (info.fieldType==GF_SG_VRML_SFCOMMANDBUFFER) { | |
| u32 j, count2; | |
| SFCommandBuffer *cb = (SFCommandBuffer*)info.far_ptr; | |
| count2 = gf_list_count(cb->commandList); | |
| for (j=0; j<count2; j++) { | |
| u32 k = 0; | |
| GF_CommandField *f; | |
| GF_Command *com = gf_list_get(cb->commandList, j); | |
| while ((f=gf_list_enum(com->command_fields, &k))) { | |
| switch (f->fieldType) { | |
| case GF_SG_VRML_SFNODE: | |
| if (f->new_node) { | |
| if (f->new_node->sgprivate->scenegraph==sg) { | |
| /*if root of graph, skip*/ | |
| if (sg->RootNode!=f->new_node) { | |
| gf_node_unregister(f->new_node, node); | |
| /*don't forget to remove node...*/ | |
| f->new_node = NULL; | |
| } | |
| } else { | |
| SG_GraphRemoved(f->new_node, sg); | |
| } | |
| } | |
| break; | |
| case GF_SG_VRML_MFNODE: | |
| if (f->node_list) { | |
| GF_ChildNodeItem *cur, *prev, *list = f->node_list; | |
| prev = NULL; | |
| while (list) { | |
| if (list->node->sgprivate->scenegraph==sg) { | |
| gf_node_unregister(list->node, node); | |
| if (prev) prev->next = list->next; | |
| else f->node_list = list->next; | |
| cur = list; | |
| gf_free(cur); | |
| } else { | |
| SG_GraphRemoved(list->node, sg); | |
| } | |
| list = list->next; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| GFINLINE GF_Node *SG_SearchForNode(GF_SceneGraph *sg, GF_Node *node) | |
| { | |
| NodeIDedItem *reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->node == node) return reg_node->node; | |
| reg_node = reg_node->next; | |
| } | |
| return NULL; | |
| } | |
| static GFINLINE u32 get_num_id_nodes(GF_SceneGraph *sg) | |
| { | |
| u32 count = 0; | |
| NodeIDedItem *reg_node = sg->id_node; | |
| while (reg_node) { | |
| count++; | |
| reg_node = reg_node->next; | |
| } | |
| return count; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| static void drop_proto_default_field_nodes(GF_Proto *p) | |
| { | |
| u32 fi, fcount = gf_list_count(p->proto_fields); | |
| for (fi = 0; fi < fcount; fi++) { | |
| GF_ProtoFieldInterface *field = (GF_ProtoFieldInterface*)gf_list_get(p->proto_fields, fi); | |
| if ((field->FieldType==GF_SG_VRML_SFNODE) && field->def_sfnode_value) { | |
| gf_node_unregister(field->def_sfnode_value, NULL); | |
| field->def_sfnode_value = NULL; | |
| } | |
| else if ((field->FieldType==GF_SG_VRML_MFNODE) && field->def_mfnode_value) { | |
| gf_node_unregister_children(NULL, field->def_mfnode_value); | |
| field->def_mfnode_value = NULL; | |
| } | |
| } | |
| } | |
| static void drop_nested_node_codes(GF_SceneGraph* sg) | |
| { | |
| u32 pi, pcount; | |
| pcount = gf_list_count(sg->protos); | |
| for (pi = 0; pi < pcount; pi++) { | |
| GF_Proto* p = (GF_Proto*)gf_list_get(sg->protos, pi); | |
| while (gf_list_count(p->node_code)) { | |
| GF_Node* n = (GF_Node*)gf_list_pop_back(p->node_code); | |
| if (n->sgprivate->referencing_protos) | |
| gf_list_del_item(n->sgprivate->referencing_protos, p); | |
| gf_node_unregister(n, NULL); | |
| } | |
| drop_proto_default_field_nodes(p); | |
| if (p->sub_graph) | |
| drop_nested_node_codes(p->sub_graph); | |
| } | |
| pcount = gf_list_count(sg->unregistered_protos); | |
| for (pi = 0; pi < pcount; pi++) { | |
| GF_Proto* p = (GF_Proto*)gf_list_get(sg->unregistered_protos, pi); | |
| while (gf_list_count(p->node_code)) { | |
| GF_Node* n = (GF_Node*)gf_list_pop_back(p->node_code); | |
| if (n->sgprivate->referencing_protos) | |
| gf_list_del_item(n->sgprivate->referencing_protos, p); | |
| gf_node_unregister(n, NULL); | |
| } | |
| drop_proto_default_field_nodes(p); | |
| if (p->sub_graph) | |
| drop_nested_node_codes(p->sub_graph); | |
| } | |
| } | |
| #endif | |
| GF_EXPORT | |
| void gf_sg_reset(GF_SceneGraph *sg) | |
| { | |
| GF_SceneGraph *par; | |
| GF_List *gc; | |
| #ifndef GPAC_DISABLE_SVG | |
| u32 type; | |
| #endif | |
| u32 count; | |
| NodeIDedItem *reg_node; | |
| if (!sg) return; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_SCENE, ("[SceneGraph] resetting scene graph\n")); | |
| #if 0 | |
| /*inline graph, remove any of this graph nodes from the parent graph*/ | |
| if (!sg->pOwningProto && sg->parent_scene) { | |
| GF_SceneGraph *par = sg->parent_scene; | |
| while (par->parent_scene) par = par->parent_scene; | |
| if (par->RootNode) SG_GraphRemoved(par->RootNode, sg); | |
| } | |
| #endif | |
| gc = gf_list_new(); | |
| #ifdef GPAC_HAS_QJS | |
| /*scripts are the first source of cylic references in the graph. In order to clean properly | |
| force a remove of all script nodes, this will release all references to nodes in JS*/ | |
| while (gf_list_count(sg->scripts)) { | |
| GF_Node *n = gf_list_get(sg->scripts, 0); | |
| gf_list_rem(sg->scripts, 0); | |
| /*prevent destroy*/ | |
| gf_node_register(n, NULL); | |
| /*remove from all parents*/ | |
| gf_node_replace(n, NULL, 0); | |
| /*FORCE destroy in case the script refers to itself*/ | |
| n->sgprivate->num_instances=1; | |
| gf_node_unregister(n, NULL); | |
| /*remember this script was forced to be destroyed*/ | |
| gf_list_add(gc, n); | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_mx_p(sg->dom_evt_mx); | |
| /*remove listeners attached to the doc*/ | |
| gf_dom_event_remove_all_listeners(sg->dom_evt, sg); | |
| /*flush any pending add_listener*/ | |
| gf_dom_listener_reset_deferred(sg); | |
| gf_mx_v(sg->dom_evt_mx); | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| while (gf_list_count(sg->routes_to_activate)) { | |
| gf_list_rem(sg->routes_to_activate, 0); | |
| } | |
| /*destroy all routes*/ | |
| while (gf_list_count(sg->Routes)) { | |
| GF_Route *r = (GF_Route*)gf_list_get(sg->Routes, 0); | |
| /*this will unregister the route from the graph, so don't delete the chain entry*/ | |
| gf_sg_route_del(r); | |
| } | |
| #endif | |
| /*reset all exported symbols */ | |
| while (gf_list_count(sg->exported_nodes)) { | |
| GF_Node *n = gf_list_get(sg->exported_nodes, 0); | |
| gf_list_rem(sg->exported_nodes, 0); | |
| gf_node_replace(n, NULL, 0); | |
| } | |
| /*reassign the list of exported nodes to our garbage collected nodes*/ | |
| gf_list_del(sg->exported_nodes); | |
| sg->exported_nodes = gc; | |
| /*reset the main tree*/ | |
| if (sg->RootNode) gf_node_unregister(sg->RootNode, NULL); | |
| sg->RootNode = NULL; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (!sg->pOwningProto && gf_list_count(sg->protos) && sg->GetExternProtoLib) | |
| sg->GetExternProtoLib(sg->userpriv, NULL); | |
| /* Drop node_code refs from all protos before force-freeing nodes below, | |
| preventing UAF when gf_sg_proto_del unregisters the same nodes */ | |
| drop_nested_node_codes(sg); | |
| #endif | |
| /*WATCHOUT: we may have cyclic dependencies due to | |
| 1- a node referencing itself (forbidden in VRML) | |
| 2- nodes referred to in commands of conditionals children of this node (MPEG-4 is mute about that) | |
| we recursively preocess from last declared DEF node to first one | |
| */ | |
| restart: | |
| reg_node = sg->id_node; | |
| while (reg_node) { | |
| #if 0 | |
| Bool ignore = 0; | |
| #endif | |
| GF_ParentList *nlist; | |
| GF_Node *node = reg_node->node; | |
| if (!node | |
| #ifndef GPAC_DISABLE_VRML | |
| || (node==sg->global_qp) | |
| #endif | |
| ) { | |
| reg_node = reg_node->next; | |
| continue; | |
| } | |
| /*first replace all instances in parents by NULL WITHOUT UNREGISTERING (to avoid destroying the node). | |
| This will take care of nodes referencing themselves*/ | |
| nlist = node->sgprivate->parents; | |
| #ifndef GPAC_DISABLE_SVG | |
| type = (node->sgprivate->tag>GF_NODE_RANGE_LAST_VRML) ? 1 : 0; | |
| #endif | |
| while (nlist) { | |
| GF_ParentList *next = nlist->next; | |
| #if 0 | |
| /*parent is a DEF'ed node, try to clean-up properly?*/ | |
| if ((nlist->node!=node) && SG_SearchForNode(sg, nlist->node) != NULL) { | |
| ignore = 1; | |
| break; | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| if (type) { | |
| ReplaceIRINode(nlist->node, node, NULL); | |
| } else { | |
| #endif | |
| if (gf_list_find(sg->exported_nodes, nlist->node)<0) { | |
| ReplaceDEFNode(nlist->node, reg_node->node, NULL, 0); | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| } | |
| #endif | |
| /*direct cyclic reference to ourselves, make sure we update the parentList to the next entry before freeing it | |
| since the next parent node could be reg_node again (reg_node->reg_node)*/ | |
| if (nlist->node==node) { | |
| node->sgprivate->parents = next; | |
| } | |
| gf_free(nlist); | |
| nlist = next; | |
| } | |
| node->sgprivate->parents = NULL; | |
| //sg->node_registry[i-1] = NULL; | |
| count = get_num_id_nodes(sg); | |
| node->sgprivate->num_instances = 1; | |
| /*remember this node was forced to be destroyed*/ | |
| gf_list_add(sg->exported_nodes, node); | |
| gf_node_unregister(node, NULL); | |
| if (count != get_num_id_nodes(sg)) goto restart; | |
| reg_node = reg_node->next; | |
| } | |
| /*reset the forced destroy ndoes*/ | |
| gf_list_reset(sg->exported_nodes); | |
| #ifndef GPAC_DISABLE_VRML | |
| /*destroy all proto*/ | |
| while (gf_list_count(sg->protos)) { | |
| GF_Proto *p = (GF_Proto *)gf_list_get(sg->protos, 0); | |
| /*this will unregister the proto from the graph, so don't delete the chain entry*/ | |
| gf_sg_proto_del(p); | |
| } | |
| /*destroy all unregistered proto*/ | |
| while (gf_list_count(sg->unregistered_protos)) { | |
| GF_Proto *p = (GF_Proto *)gf_list_get(sg->unregistered_protos, 0); | |
| /*this will unregister the proto from the graph, so don't delete the chain entry*/ | |
| gf_sg_proto_del(p); | |
| } | |
| /*last destroy all routes*/ | |
| gf_sg_destroy_routes(sg); | |
| sg->simulation_tick = 0; | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| while (gf_list_count(sg->ns)) { | |
| GF_XMLNS *ns = gf_list_get(sg->ns, 0); | |
| gf_list_rem(sg->ns, 0); | |
| if (ns->name) gf_free(ns->name); | |
| if (ns->qname) gf_free(ns->qname); | |
| gf_free(ns); | |
| } | |
| gf_list_del(sg->ns); | |
| sg->ns = 0; | |
| par = sg; | |
| while (par->parent_scene) par = par->parent_scene; | |
| #ifndef GPAC_DISABLE_SVG | |
| if (par != sg) { | |
| u32 i; | |
| count = gf_list_count(par->smil_timed_elements); | |
| for (i=0; i<count; i++) { | |
| SMIL_Timing_RTI *rti = gf_list_get(par->smil_timed_elements, i); | |
| if (rti->timed_elt->sgprivate->scenegraph == sg) { | |
| gf_list_rem(par->smil_timed_elements, i); | |
| i--; | |
| count--; | |
| } | |
| } | |
| } | |
| #endif | |
| #ifdef GF_SELF_REPLACE_ENABLE | |
| sg->graph_has_been_reset = 1; | |
| #endif | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_SCENE, ("[SceneGraph] Scene graph has been reset\n")); | |
| } | |
| GFINLINE GF_Node *SG_SearchForDuplicateNodeID(GF_SceneGraph *sg, u32 nodeID, GF_Node *toExclude) | |
| { | |
| NodeIDedItem *reg_node = sg->id_node; | |
| while (reg_node) { | |
| if ((reg_node->node != toExclude) && (reg_node->NodeID == nodeID)) return reg_node->node; | |
| reg_node = reg_node->next; | |
| } | |
| return NULL; | |
| } | |
| #if 0 //unused | |
| void *gf_node_get_name_address(GF_Node*node) | |
| { | |
| NodeIDedItem *reg_node; | |
| if (!(node->sgprivate->flags & GF_NODE_IS_DEF)) return NULL; | |
| reg_node = node->sgprivate->scenegraph->id_node; | |
| while (reg_node) { | |
| if (reg_node->node == node) return ®_node->NodeName; | |
| reg_node = reg_node->next; | |
| } | |
| return NULL; | |
| } | |
| #endif | |
| GF_EXPORT | |
| void gf_sg_set_private(GF_SceneGraph *sg, void *ptr) | |
| { | |
| if (sg) sg->userpriv = ptr; | |
| } | |
| GF_EXPORT | |
| void *gf_sg_get_private(GF_SceneGraph *sg) | |
| { | |
| return sg ? sg->userpriv : NULL; | |
| } | |
| GF_EXPORT | |
| void gf_sg_set_scene_size_info(GF_SceneGraph *sg, u32 width, u32 height, Bool usePixelMetrics) | |
| { | |
| if (!sg) return; | |
| if (width && height) { | |
| sg->width = width; | |
| sg->height = height; | |
| } else { | |
| sg->width = sg->height = 0; | |
| } | |
| sg->usePixelMetrics = usePixelMetrics; | |
| } | |
| GF_EXPORT | |
| Bool gf_sg_use_pixel_metrics(GF_SceneGraph *sg) | |
| { | |
| if (sg) { | |
| #ifndef GPAC_DISABLE_VRML | |
| while (sg->pOwningProto) sg = sg->parent_scene; | |
| #endif | |
| return sg->usePixelMetrics; | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| Bool gf_sg_get_scene_size_info(GF_SceneGraph *sg, u32 *width, u32 *height) | |
| { | |
| if (!sg) return 0; | |
| if (width) *width = sg->width; | |
| if (height) *height = sg->height; | |
| return (sg->width && sg->height) ? 1 : 0; | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_sg_get_root_node(GF_SceneGraph *sg) | |
| { | |
| return sg ? sg->RootNode : NULL; | |
| } | |
| GF_EXPORT | |
| void gf_sg_set_root_node(GF_SceneGraph *sg, GF_Node *node) | |
| { | |
| if (sg) sg->RootNode = node; | |
| } | |
| void remove_node_id(GF_SceneGraph *sg, GF_Node *node) | |
| { | |
| NodeIDedItem *reg_node = sg->id_node; | |
| if (!reg_node) return; | |
| if (reg_node->node==node) { | |
| sg->id_node = reg_node->next; | |
| if (sg->id_node_last==reg_node) | |
| sg->id_node_last = reg_node->next; | |
| if (reg_node->NodeName) gf_free(reg_node->NodeName); | |
| gf_free(reg_node); | |
| } else { | |
| NodeIDedItem *to_del; | |
| while (reg_node->next) { | |
| if (reg_node->next->node!=node) { | |
| reg_node = reg_node->next; | |
| continue; | |
| } | |
| to_del = reg_node->next; | |
| reg_node->next = to_del->next; | |
| if (sg->id_node_last==to_del) { | |
| sg->id_node_last = reg_node->next ? reg_node->next : reg_node; | |
| } | |
| if (to_del->NodeName) gf_free(to_del->NodeName); | |
| to_del->NodeName = NULL; | |
| gf_free(to_del); | |
| break; | |
| } | |
| } | |
| } | |
| GF_Err gf_node_try_destroy(GF_SceneGraph *sg, GF_Node *pNode, GF_Node *parentNode) | |
| { | |
| if (!pNode || !sg) return GF_OK; | |
| if (gf_list_find(sg->exported_nodes, pNode) >= 0) return GF_OK; | |
| if (!pNode->sgprivate->num_instances) return GF_OK; | |
| return gf_node_unregister(pNode, parentNode); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_unregister(GF_Node *pNode, GF_Node *parentNode) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 j; | |
| #endif | |
| #ifdef GPAC_HAS_QJS | |
| Bool detach=0; | |
| #endif | |
| GF_SceneGraph *pSG; | |
| if (!pNode) return GF_OK; | |
| pSG = pNode->sgprivate->scenegraph; | |
| if (parentNode) { | |
| GF_ParentList *nlist = pNode->sgprivate->parents; | |
| if (nlist) { | |
| GF_ParentList *prev = NULL; | |
| while (nlist) { | |
| if (nlist->node != parentNode) { | |
| prev = nlist; | |
| nlist = nlist->next; | |
| continue; | |
| } | |
| if (prev) prev->next = nlist->next; | |
| else pNode->sgprivate->parents = nlist->next; | |
| gf_free(nlist); | |
| #ifdef GPAC_HAS_QJS | |
| if (pNode->sgprivate->parents==NULL) detach=1; | |
| #endif | |
| break; | |
| } | |
| } | |
| if (parentNode->sgprivate->scenegraph != pSG) { | |
| gf_list_del_item(pSG->exported_nodes, pNode); | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto its is registered in its parent graph, not the current*/ | |
| if (pSG && (pNode == (GF_Node*)pSG->pOwningProto)) pSG = pSG->parent_scene; | |
| #endif | |
| /*unregister the instance*/ | |
| if (!pNode->sgprivate->num_instances) | |
| return GF_BAD_PARAM; | |
| pNode->sgprivate->num_instances -= 1; | |
| /*this is just an instance removed*/ | |
| if (pNode->sgprivate->num_instances) { | |
| #ifdef GPAC_HAS_QJS | |
| if (pNode->sgprivate->num_instances==1) detach=1; | |
| if (pSG && pNode->sgprivate->scenegraph->on_node_modified && detach && pNode->sgprivate->interact && pNode->sgprivate->interact->js_binding) { | |
| pNode->sgprivate->scenegraph->on_node_modified(pNode->sgprivate->scenegraph, pNode, NULL, NULL); | |
| } | |
| #endif | |
| return GF_OK; | |
| } | |
| gf_assert(pNode->sgprivate->parents==NULL); | |
| if (pSG) { | |
| /*if def, remove from sg def table*/ | |
| if (pNode->sgprivate->flags & GF_NODE_IS_DEF) { | |
| remove_node_id(pSG, pNode); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*check all routes from or to this node and destroy them - cf spec*/ | |
| GF_Route *r; | |
| j=0; | |
| while ((r = (GF_Route *)gf_list_enum(pSG->Routes, &j))) { | |
| if ( (r->ToNode == pNode) || (r->FromNode == pNode)) { | |
| gf_sg_route_del(r); | |
| j--; | |
| } | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| if (pSG->use_stack && (gf_list_del_item(pSG->use_stack, pNode)>=0)) { | |
| pSG->abort_bubbling = 1; | |
| } | |
| #endif | |
| } | |
| /*delete the node*/ | |
| pSG = pNode->sgprivate->scenegraph; | |
| while (pSG) { | |
| if (pSG->RootNode == pNode) | |
| pSG->RootNode = NULL; | |
| pSG = pSG->parent_scene; | |
| } | |
| gf_node_del(pNode); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_register(GF_Node *node, GF_Node *parentNode) | |
| { | |
| if (!node) return GF_OK; | |
| if (!node->sgprivate || node->sgprivate->num_instances >= 0xFFFF) return GF_BAD_PARAM; | |
| node->sgprivate->num_instances ++; | |
| /*parent may be NULL (top node and proto)*/ | |
| if (parentNode) { | |
| if (!node->sgprivate->parents) { | |
| node->sgprivate->parents = (GF_ParentList*)gf_malloc(sizeof(GF_ParentList)); | |
| node->sgprivate->parents->next = NULL; | |
| node->sgprivate->parents->node = parentNode; | |
| } else { | |
| GF_ParentList *item, *nlist = node->sgprivate->parents; | |
| while (nlist->next) nlist = nlist->next; | |
| item = (GF_ParentList*)gf_malloc(sizeof(GF_ParentList)); | |
| item->next = NULL; | |
| item->node = parentNode; | |
| nlist->next = item; | |
| } | |
| if (parentNode->sgprivate->scenegraph != node->sgprivate->scenegraph) { | |
| gf_list_add(node->sgprivate->scenegraph->exported_nodes, node); | |
| } | |
| } | |
| return GF_OK; | |
| } | |
| /*replace or remove node instance in the given node (eg in all GF_Node or MFNode fields) | |
| this doesn't propagate in the scene graph. If updateOrderedGroup and new_node is NULL, the order field of OG | |
| is updated*/ | |
| static void ReplaceDEFNode(GF_Node *FromNode, GF_Node *node, GF_Node *newNode, Bool updateOrderedGroup) | |
| { | |
| u32 i, j, count; | |
| GF_Node *p; | |
| GF_ChildNodeItem *list; | |
| GF_FieldInfo field; | |
| /*browse all fields*/ | |
| count = gf_node_get_field_count(FromNode); | |
| for (i=0; i<count; i++) { | |
| gf_node_get_field(FromNode, i, &field); | |
| switch (field.fieldType) { | |
| case GF_SG_VRML_SFNODE: | |
| /*set to NULL for SFFields*/ | |
| p = *((GF_Node **) field.far_ptr); | |
| /*this is a USE / DEF*/ | |
| if (p == node) { | |
| *((GF_Node **) field.far_ptr) = NULL; | |
| if (newNode) { | |
| *((GF_Node **) field.far_ptr) = newNode; | |
| } | |
| goto exit; | |
| } | |
| break; | |
| case GF_SG_VRML_MFNODE: | |
| list = *(GF_ChildNodeItem **) field.far_ptr; | |
| j=0; | |
| while (list) { | |
| /*replace nodes different from newNode but with same ID*/ | |
| if ((newNode == list->node) || (list->node != node)) { | |
| list = list->next; | |
| j++; | |
| continue; | |
| } | |
| if (newNode) { | |
| list->node = newNode; | |
| } else { | |
| gf_node_list_del_child( (GF_ChildNodeItem **) field.far_ptr, list->node); | |
| #ifndef GPAC_DISABLE_VRML | |
| if (updateOrderedGroup && (FromNode->sgprivate->tag==TAG_MPEG4_OrderedGroup)) { | |
| GF_FieldInfo info; | |
| M_OrderedGroup *og = (M_OrderedGroup *)FromNode; | |
| info.fieldIndex = 3; | |
| info.fieldType = GF_SG_VRML_MFFLOAT; | |
| info.on_event_in = NULL; | |
| info.far_ptr = &og->order; | |
| gf_sg_vrml_mf_remove(&og->order, GF_SG_VRML_SFINT32, j); | |
| gf_node_changed_internal(FromNode, &info, 1); | |
| } | |
| #endif | |
| } | |
| goto exit; | |
| } | |
| break; | |
| /*not a node, continue*/ | |
| default: | |
| continue; | |
| } | |
| } | |
| /*since we don't filter parent nodes this is called once per USE, not per container, so return if found*/ | |
| exit: | |
| #ifndef GPAC_DISABLE_VRML | |
| /*Notify all scripts that the node is being removed from its parent - we have to do that because nodes used in scripts are not | |
| always registered in MF/SFNodes fields of the script.*/ | |
| switch (FromNode->sgprivate->tag) { | |
| case TAG_MPEG4_Script: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Script: | |
| #endif | |
| if (FromNode->sgprivate->scenegraph->on_node_modified) | |
| FromNode->sgprivate->scenegraph->on_node_modified(FromNode->sgprivate->scenegraph, node, NULL, FromNode); | |
| break; | |
| } | |
| #endif | |
| gf_node_changed(FromNode, &field); | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| static void Replace_IRI(GF_SceneGraph *sg, GF_Node *old_node, GF_Node *newNode) | |
| { | |
| u32 i, count; | |
| count = gf_list_count(sg->xlink_hrefs); | |
| for (i=0; i<count; i++) { | |
| XMLRI *iri = (XMLRI *)gf_list_get(sg->xlink_hrefs, i); | |
| if (iri->target == old_node) { | |
| iri->target = newNode; | |
| if (!newNode) { | |
| gf_list_rem(sg->xlink_hrefs, i); | |
| i--; | |
| count--; | |
| } | |
| } | |
| } | |
| } | |
| /*replace or remove node instance in the given node (eg in all IRI)*/ | |
| static void ReplaceIRINode(GF_Node *FromNode, GF_Node *old_node, GF_Node *newNode) | |
| { | |
| GF_ChildNodeItem *prev = NULL; | |
| GF_ChildNodeItem *child = ((SVG_Element *)FromNode)->children; | |
| while (child) { | |
| if (child->node != old_node) { | |
| prev = child; | |
| child = child->next; | |
| continue; | |
| } | |
| if (newNode) { | |
| child->node = newNode; | |
| } else { | |
| if (prev) prev->next = child->next; | |
| else ((SVG_Element *)FromNode)->children = child->next; | |
| gf_free(child); | |
| } | |
| break; | |
| } | |
| } | |
| #endif | |
| /*get all parents of the node and replace, the instance of the node and finally destroy the node*/ | |
| GF_EXPORT | |
| GF_Err gf_node_replace(GF_Node *node, GF_Node *new_node, Bool updateOrderedGroup) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| u32 type; | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| Bool replace_proto; | |
| #endif | |
| Bool replace_root; | |
| if (!node || !node->sgprivate || !node->sgprivate->scenegraph) | |
| return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_SVG | |
| type = (node->sgprivate->tag>GF_NODE_RANGE_LAST_VRML) ? 1 : 0; | |
| if (type) { | |
| Replace_IRI(node->sgprivate->scenegraph, node, new_node); | |
| } | |
| #endif | |
| /*first check if this is the root node*/ | |
| replace_root = (node->sgprivate->scenegraph->RootNode == node) ? 1 : 0; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*check for proto replacement*/ | |
| replace_proto = 0; | |
| if (node->sgprivate->scenegraph->pOwningProto | |
| && (gf_list_find(node->sgprivate->scenegraph->pOwningProto->node_code, node)>=0)) { | |
| replace_proto = 1; | |
| } | |
| #endif | |
| while (node->sgprivate->parents) { | |
| Bool do_break = node->sgprivate->parents->next ? 0 : 1; | |
| GF_Node *par = node->sgprivate->parents->node; | |
| #ifndef GPAC_DISABLE_SVG | |
| if (type) { | |
| ReplaceIRINode(par, node, new_node); | |
| } | |
| else { | |
| #endif | |
| ReplaceDEFNode(par, node, new_node, updateOrderedGroup); | |
| } | |
| if (new_node && new_node != par) gf_node_register(new_node, par); | |
| if (node != par) | |
| gf_node_changed(par, NULL); | |
| gf_node_unregister(node, par); | |
| if (do_break) break; | |
| } | |
| if (replace_root) { | |
| gf_node_register(new_node, NULL); | |
| GF_SceneGraph *pSG = node->sgprivate->scenegraph; | |
| gf_node_unregister(node, NULL); | |
| pSG->RootNode = new_node; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| if (replace_proto) { | |
| GF_SceneGraph *pSG = node->sgprivate->scenegraph; | |
| gf_list_del_item(pSG->pOwningProto->node_code, node); | |
| if (node->sgprivate->referencing_protos) | |
| gf_list_del_item(node->sgprivate->referencing_protos, pSG->pOwningProto); | |
| if (pSG->pOwningProto->RenderingNode==node) pSG->pOwningProto->RenderingNode = NULL; | |
| gf_node_unregister(node, NULL); | |
| } | |
| #endif | |
| return GF_OK; | |
| } | |
| static GFINLINE void insert_node_def(GF_SceneGraph *sg, GF_Node *def, u32 ID, const char *name) | |
| { | |
| NodeIDedItem *reg_node, *cur; | |
| reg_node = (NodeIDedItem *) gf_malloc(sizeof(NodeIDedItem)); | |
| reg_node->node = def; | |
| reg_node->NodeID = ID; | |
| reg_node->NodeName = name ? gf_strdup(name) : NULL; | |
| if (!sg->id_node) { | |
| sg->id_node = reg_node; | |
| sg->id_node_last = sg->id_node; | |
| reg_node->next = NULL; | |
| } else if (sg->id_node_last->NodeID < ID) { | |
| sg->id_node_last->next = reg_node; | |
| sg->id_node_last = reg_node; | |
| reg_node->next = NULL; | |
| } else if (sg->id_node->NodeID>ID) { | |
| reg_node->next = sg->id_node; | |
| sg->id_node = reg_node; | |
| } else { | |
| cur = sg->id_node; | |
| while (cur->next) { | |
| if (cur->next->NodeID>ID) { | |
| reg_node->next = cur->next; | |
| cur->next = reg_node; | |
| return; | |
| } | |
| cur = cur->next; | |
| } | |
| cur->next = reg_node; | |
| sg->id_node_last = reg_node; | |
| reg_node->next = NULL; | |
| } | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_set_id(GF_Node *p, u32 ID, const char *name) | |
| { | |
| GF_SceneGraph *pSG; | |
| if (!ID || !p || !p->sgprivate->scenegraph) return GF_BAD_PARAM; | |
| pSG = p->sgprivate->scenegraph; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto register to the parent graph, not the current*/ | |
| if (p == (GF_Node*)pSG->pOwningProto) pSG = pSG->parent_scene; | |
| #endif | |
| /*new DEF ID*/ | |
| if (!(p->sgprivate->flags & GF_NODE_IS_DEF) ) { | |
| p->sgprivate->flags |= GF_NODE_IS_DEF; | |
| insert_node_def(pSG, p, ID, name); | |
| } | |
| /*reassigning ID, remove node def*/ | |
| else { | |
| char *_name = gf_strdup(name); | |
| remove_node_id(pSG, p); | |
| insert_node_def(pSG, p, ID, _name); | |
| gf_free(_name); | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_remove_id(GF_Node *p) | |
| { | |
| GF_SceneGraph *pSG; | |
| if (!p) return GF_BAD_PARAM; | |
| pSG = p->sgprivate->scenegraph; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto register to the parent graph, not the current*/ | |
| if (p == (GF_Node*)pSG->pOwningProto) pSG = pSG->parent_scene; | |
| #endif | |
| /*new DEF ID*/ | |
| if (p->sgprivate->flags & GF_NODE_IS_DEF) { | |
| remove_node_id(pSG, p); | |
| p->sgprivate->flags &= ~GF_NODE_IS_DEF; | |
| return GF_OK; | |
| } | |
| return GF_BAD_PARAM; | |
| } | |
| /*calls RenderNode on this node*/ | |
| GF_EXPORT | |
| void gf_node_traverse(GF_Node *node, void *renderStack) | |
| { | |
| if (!node || !node->sgprivate) return; | |
| if (node->sgprivate->flags & GF_NODE_IS_DEACTIVATED) return; | |
| if (node->sgprivate->UserCallback) { | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| if (node->sgprivate->flags & GF_NODE_IN_TRAVERSE) return; | |
| node->sgprivate->flags |= GF_NODE_IN_TRAVERSE; | |
| gf_assert(node->sgprivate->flags); | |
| #endif | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_SCENE, ("[SceneGraph] Traversing node %s (ID %s)\n", gf_node_get_class_name(node) , gf_node_get_name(node) )); | |
| node->sgprivate->UserCallback(node, renderStack, 0); | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| node->sgprivate->flags &= ~GF_NODE_IN_TRAVERSE; | |
| #endif | |
| return; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| if (node->sgprivate->tag != TAG_ProtoNode) return; | |
| /*if no rendering function is assigned this is a real proto (otherwise this is an hardcoded one)*/ | |
| if (!node->sgprivate->UserCallback) { | |
| /*if a rendering node is assigned use it*/ | |
| if (((GF_ProtoInstance *) node)->RenderingNode) { | |
| node = ((GF_ProtoInstance *) node)->RenderingNode; | |
| /*if rendering node is a proto and not a hardcoded proto, traverse it*/ | |
| if (!node->sgprivate->UserCallback && (node->sgprivate->tag == TAG_ProtoNode)) { | |
| gf_node_traverse(node, renderStack); | |
| return; | |
| } | |
| } | |
| /*if no rendering node, check if the proto is fully instantiated (externProto)*/ | |
| else { | |
| GF_ProtoInstance *proto_inst = (GF_ProtoInstance *) node; | |
| gf_node_dirty_clear(node, 0); | |
| /*proto has been deleted or dummy proto (without node code)*/ | |
| if (!proto_inst->proto_interface || (proto_inst->flags & GF_SG_PROTO_LOADED) ) return; | |
| /*try to load the code*/ | |
| gf_sg_proto_instantiate(proto_inst); | |
| /*if user callback is set, this is an hardcoded proto. If not, locate the first traversable node*/ | |
| if (!node->sgprivate->UserCallback) { | |
| if (!proto_inst->RenderingNode) { | |
| gf_node_dirty_set(node, 0, 1); | |
| return; | |
| } | |
| /*signal we have been loaded*/ | |
| node->sgprivate->scenegraph->NodeCallback(node->sgprivate->scenegraph->userpriv, GF_SG_CALLBACK_MODIFIED, node, NULL); | |
| } | |
| } | |
| } | |
| if (node->sgprivate->UserCallback) { | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| if (node->sgprivate->flags & GF_NODE_IN_TRAVERSE) return; | |
| node->sgprivate->flags |= GF_NODE_IN_TRAVERSE; | |
| #endif | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_SCENE, ("[SceneGraph] Traversing node %s\n", gf_node_get_class_name(node) )); | |
| node->sgprivate->UserCallback(node, renderStack, 0); | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| node->sgprivate->flags &= ~GF_NODE_IN_TRAVERSE; | |
| #endif | |
| } | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| } | |
| GF_EXPORT | |
| void gf_node_allow_cyclic_traverse(GF_Node *node) | |
| { | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| if (node) node->sgprivate->flags &= ~GF_NODE_IN_TRAVERSE; | |
| #endif | |
| } | |
| GF_EXPORT | |
| Bool gf_node_set_cyclic_traverse_flag(GF_Node *node, Bool on) | |
| { | |
| Bool ret = 1; | |
| #ifdef GF_CYCLIC_TRAVERSE_ON | |
| if (node) { | |
| ret = (node->sgprivate->flags & GF_NODE_IN_TRAVERSE) ? 0 : 1; | |
| if (on) { | |
| node->sgprivate->flags |= GF_NODE_IN_TRAVERSE; | |
| } else { | |
| node->sgprivate->flags &= ~GF_NODE_IN_TRAVERSE; | |
| } | |
| } | |
| #endif | |
| return ret; | |
| } | |
| /*blindly calls RenderNode on all nodes in the "children" list*/ | |
| GF_EXPORT | |
| void gf_node_traverse_children(GF_Node *node, void *renderStack) | |
| { | |
| GF_ChildNodeItem *child; | |
| gf_assert(node); | |
| child = ((GF_ParentNode *)node)->children; | |
| while (child) { | |
| gf_node_traverse(child->node, renderStack); | |
| child = child->next; | |
| } | |
| } | |
| GF_EXPORT | |
| GF_SceneGraph *gf_node_get_graph(GF_Node *node) | |
| { | |
| return (node ? node->sgprivate->scenegraph : NULL); | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_sg_find_node(GF_SceneGraph *sg, u32 nodeID) | |
| { | |
| NodeIDedItem *reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->NodeID == nodeID) return reg_node->node; | |
| reg_node = reg_node->next; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_sg_find_node_by_name(GF_SceneGraph *sg, char *name) | |
| { | |
| if (name) { | |
| NodeIDedItem *reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->NodeName && !strcmp(reg_node->NodeName, name)) return reg_node->node; | |
| reg_node = reg_node->next; | |
| } | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| u32 gf_sg_get_next_available_node_id(GF_SceneGraph *sg) | |
| { | |
| u32 ID; | |
| NodeIDedItem *reg_node; | |
| if (!sg->id_node) return 1; | |
| reg_node = sg->id_node; | |
| ID = reg_node->NodeID; | |
| /*nodes are sorted*/ | |
| while (reg_node->next) { | |
| if (ID+1<reg_node->next->NodeID) return ID+1; | |
| ID = reg_node->next->NodeID; | |
| reg_node = reg_node->next; | |
| } | |
| return ID+1; | |
| } | |
| GF_EXPORT | |
| u32 gf_sg_get_max_node_id(GF_SceneGraph *sg) | |
| { | |
| NodeIDedItem *reg_node; | |
| if (!sg->id_node) return 0; | |
| if (sg->id_node_last) return sg->id_node_last->NodeID; | |
| reg_node = sg->id_node; | |
| while (reg_node->next) reg_node = reg_node->next; | |
| return reg_node->NodeID; | |
| } | |
| void gf_node_setup(GF_Node *p, u32 tag) | |
| { | |
| if (!p) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneGraph] Failed to setup NULL node\n")); | |
| return; | |
| } | |
| GF_SAFEALLOC(p->sgprivate, NodePriv); | |
| if (!p->sgprivate) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneGraph] Failed to allocate node scenegraph private handler\n")); | |
| return; | |
| } | |
| p->sgprivate->tag = tag; | |
| p->sgprivate->flags = GF_SG_NODE_DIRTY; | |
| } | |
| GF_Node *gf_sg_new_base_node() | |
| { | |
| GF_Node *newnode = (GF_Node *)gf_malloc(sizeof(GF_Node)); | |
| gf_node_setup(newnode, TAG_UndefinedNode); | |
| return newnode; | |
| } | |
| GF_EXPORT | |
| u32 gf_node_get_tag(GF_Node*p) | |
| { | |
| if (!p) return (u32)-1; | |
| return p->sgprivate->tag; | |
| } | |
| GF_EXPORT | |
| u32 gf_node_get_id(GF_Node*p) | |
| { | |
| NodeIDedItem *reg_node; | |
| GF_SceneGraph *sg; | |
| gf_assert(p); | |
| if (!(p->sgprivate->flags & GF_NODE_IS_DEF)) return 0; | |
| sg = p->sgprivate->scenegraph; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto, look in parent graph*/ | |
| if (p == (GF_Node*)sg->pOwningProto) sg = sg->parent_scene; | |
| #endif | |
| reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->node==p) return reg_node->NodeID; | |
| reg_node = reg_node->next; | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| const char *gf_node_get_name(GF_Node*p) | |
| { | |
| GF_SceneGraph *sg; | |
| NodeIDedItem *reg_node; | |
| if (!p || !(p->sgprivate->flags & GF_NODE_IS_DEF)) return NULL; | |
| sg = p->sgprivate->scenegraph; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto, look in parent graph*/ | |
| if (p == (GF_Node*)sg->pOwningProto) sg = sg->parent_scene; | |
| #endif | |
| reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->node==p) return reg_node->NodeName; | |
| reg_node = reg_node->next; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| const char *gf_node_get_name_and_id(GF_Node*p, u32 *id) | |
| { | |
| GF_SceneGraph *sg; | |
| NodeIDedItem *reg_node; | |
| if (!p || !(p->sgprivate->flags & GF_NODE_IS_DEF)) { | |
| *id = 0; | |
| return NULL; | |
| } | |
| sg = p->sgprivate->scenegraph; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*if this is a proto, look in parent graph*/ | |
| if (p == (GF_Node*)sg->pOwningProto) sg = sg->parent_scene; | |
| #endif | |
| reg_node = sg->id_node; | |
| while (reg_node) { | |
| if (reg_node->node==p) { | |
| *id = reg_node->NodeID; | |
| return reg_node->NodeName; | |
| } | |
| reg_node = reg_node->next; | |
| } | |
| *id = 0; | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| void *gf_node_get_private(GF_Node*p) | |
| { | |
| gf_assert(p); | |
| return p->sgprivate->UserPrivate; | |
| } | |
| GF_EXPORT | |
| void gf_node_set_private(GF_Node*p, void *pr) | |
| { | |
| gf_assert(p); | |
| p->sgprivate->UserPrivate = pr; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_set_callback_function(GF_Node *p, void (*TraverseNode)(GF_Node *node, void *render_stack, Bool is_destroy) ) | |
| { | |
| gf_assert(p); | |
| p->sgprivate->UserCallback = TraverseNode; | |
| return GF_OK; | |
| } | |
| void gf_sg_parent_setup(GF_Node *node) | |
| { | |
| ((GF_ParentNode *)node)->children = NULL; | |
| node->sgprivate->flags |= GF_SG_CHILD_DIRTY; | |
| } | |
| GF_EXPORT | |
| void gf_node_unregister_children(GF_Node *container, GF_ChildNodeItem *child) | |
| { | |
| while (child) { | |
| GF_ChildNodeItem *cur; | |
| if (child->node != container) gf_node_unregister(child->node, container); | |
| cur = child; | |
| child = child->next; | |
| gf_free(cur); | |
| } | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_list_insert_child(GF_ChildNodeItem **list, GF_Node *n, u32 pos) | |
| { | |
| GF_ChildNodeItem *child, *cur, *prev; | |
| u32 cur_pos = 0; | |
| gf_assert(pos != (u32) -1); | |
| child = *list; | |
| cur = (GF_ChildNodeItem*) gf_malloc(sizeof(GF_ChildNodeItem)); | |
| if (!cur) return GF_OUT_OF_MEM; | |
| cur->node = n; | |
| cur->next = NULL; | |
| prev = NULL; | |
| while (child) { | |
| if (pos==cur_pos) break; | |
| /*append*/ | |
| if (!child->next) { | |
| child->next = cur; | |
| return GF_OK; | |
| } | |
| prev = child; | |
| child = child->next; | |
| cur_pos++; | |
| } | |
| cur->next = child; | |
| if (prev) prev->next = cur; | |
| else *list = cur; | |
| return GF_OK; | |
| } | |
| #if 0 //unused | |
| GF_Err gf_node_list_append_child(GF_ChildNodeItem **list, GF_ChildNodeItem **last_child, GF_Node *n) | |
| { | |
| GF_ChildNodeItem *child, *cur; | |
| child = *list; | |
| cur = (GF_ChildNodeItem*) gf_malloc(sizeof(GF_ChildNodeItem)); | |
| if (!cur) return GF_OUT_OF_MEM; | |
| cur->node = n; | |
| cur->next = NULL; | |
| if (!child) { | |
| *list = cur; | |
| *last_child = cur; | |
| } else { | |
| if (! *last_child) { | |
| while (child->next) { | |
| child = child->next; | |
| } | |
| *last_child = child; | |
| } | |
| (*last_child)->next = cur; | |
| *last_child = cur; | |
| } | |
| return GF_OK; | |
| } | |
| #endif | |
| GF_EXPORT | |
| GF_Node *gf_node_list_get_child(GF_ChildNodeItem *list, s32 pos) | |
| { | |
| s32 cur_pos = 0; | |
| while (list) { | |
| if (pos==cur_pos) return list->node; | |
| if ((pos<0) && !list->next) return list->node; | |
| list = list->next; | |
| cur_pos++; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| s32 gf_node_list_find_child(GF_ChildNodeItem *list, GF_Node *n) | |
| { | |
| s32 res = 0; | |
| while (list) { | |
| if (list->node==n) return res; | |
| list = list->next; | |
| res++; | |
| } | |
| return -1; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_list_add_child(GF_ChildNodeItem **list, GF_Node *n) | |
| { | |
| GF_ChildNodeItem *child, *cur; | |
| child = *list; | |
| cur = (GF_ChildNodeItem*) gf_malloc(sizeof(GF_ChildNodeItem)); | |
| if (!cur) return GF_OUT_OF_MEM; | |
| cur->node = n; | |
| cur->next = NULL; | |
| if (child) { | |
| while (child->next) child = child->next; | |
| child->next = cur; | |
| } else { | |
| *list = cur; | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_list_add_child_last(GF_ChildNodeItem **list, GF_Node *n, GF_ChildNodeItem **last_child) | |
| { | |
| GF_ChildNodeItem *child, *cur; | |
| child = *list; | |
| cur = (GF_ChildNodeItem*) gf_malloc(sizeof(GF_ChildNodeItem)); | |
| if (!cur) return GF_OUT_OF_MEM; | |
| cur->node = n; | |
| cur->next = NULL; | |
| if (child) { | |
| if (last_child && (*last_child) ) { | |
| while ((*last_child)->next) (*last_child) = (*last_child)->next; | |
| (*last_child)->next = cur; | |
| (*last_child) = (*last_child)->next; | |
| } else { | |
| while (child->next) child = child->next; | |
| child->next = cur; | |
| if (last_child) *last_child = child->next; | |
| } | |
| } else { | |
| *list = cur; | |
| if (last_child) | |
| *last_child = *list; | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| Bool gf_node_list_del_child(GF_ChildNodeItem **list, GF_Node *n) | |
| { | |
| GF_ChildNodeItem *child, *cur; | |
| child = *list; | |
| if (!child) return 0; | |
| if (child->node==n) { | |
| *list = child->next; | |
| gf_free(child); | |
| return 1; | |
| } | |
| while (child->next) { | |
| if (child->next->node!=n) { | |
| child = child->next; | |
| continue; | |
| } | |
| cur = child->next; | |
| child->next = cur->next; | |
| gf_free(cur); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_node_list_del_child_idx(GF_ChildNodeItem **list, u32 pos) | |
| { | |
| u32 cur_pos = 0; | |
| GF_Node *ret = NULL; | |
| GF_ChildNodeItem *child, *cur; | |
| child = *list; | |
| if (!child) return 0; | |
| if (!pos) { | |
| *list = child->next; | |
| ret = child->node; | |
| gf_free(child); | |
| return ret; | |
| } | |
| while (child->next) { | |
| if (cur_pos+1 != pos) { | |
| child = child->next; | |
| cur_pos++; | |
| continue; | |
| } | |
| cur = child->next; | |
| child->next = cur->next; | |
| ret = cur->node; | |
| gf_free(cur); | |
| return ret; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| u32 gf_node_list_get_count(GF_ChildNodeItem *list) | |
| { | |
| u32 count = 0; | |
| while (list) { | |
| count++; | |
| list = list->next; | |
| } | |
| return count; | |
| } | |
| void gf_sg_parent_reset(GF_Node *node) | |
| { | |
| gf_node_unregister_children(node, ((GF_ParentNode *)node)->children); | |
| ((GF_ParentNode *)node)->children = NULL; | |
| } | |
| void gf_node_free(GF_Node *node) | |
| { | |
| if (!node) return; | |
| if (node->sgprivate->UserCallback) node->sgprivate->UserCallback(node, NULL, 1); | |
| if (node->sgprivate->scenegraph && node->sgprivate->scenegraph->NodeCallback) | |
| node->sgprivate->scenegraph->NodeCallback(node->sgprivate->scenegraph->userpriv, GF_SG_CALLBACK_NODE_DESTROY, node, NULL); | |
| if (node->sgprivate->interact) { | |
| if (node->sgprivate->interact->routes) { | |
| u32 i, count = gf_list_count(node->sgprivate->interact->routes); | |
| for (i = 0; i < count; i++) { | |
| GF_Route* r = (GF_Route*)gf_list_get(node->sgprivate->interact->routes, i); | |
| r->FromNode = NULL; | |
| } | |
| gf_list_del(node->sgprivate->interact->routes); | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| if (node->sgprivate->interact->dom_evt) { | |
| gf_dom_event_remove_all_listeners(node->sgprivate->interact->dom_evt, node->sgprivate->scenegraph); | |
| gf_dom_event_target_del(node->sgprivate->interact->dom_evt); | |
| } | |
| if (node->sgprivate->interact->animations) { | |
| gf_list_del(node->sgprivate->interact->animations); | |
| } | |
| #endif | |
| #ifdef GPAC_HAS_QJS | |
| if (node->sgprivate->interact->js_binding) { | |
| if (node->sgprivate->scenegraph && node->sgprivate->scenegraph->on_node_modified) | |
| node->sgprivate->scenegraph->on_node_modified(node->sgprivate->scenegraph, node, NULL, NULL); | |
| gf_list_del(node->sgprivate->interact->js_binding->fields); | |
| gf_free(node->sgprivate->interact->js_binding); | |
| } | |
| #endif | |
| gf_free(node->sgprivate->interact); | |
| } | |
| if (node->sgprivate->scenegraph) { | |
| NodeIDedItem* nid = node->sgprivate->scenegraph->id_node; | |
| while (nid) { | |
| GF_ParentList* pl = nid->node->sgprivate->parents; | |
| GF_ParentList* prev_pl = NULL; | |
| while (pl) { | |
| if (pl->node == node) { | |
| GF_ParentList* next_pl = pl->next; | |
| if (prev_pl) | |
| prev_pl->next = next_pl; | |
| else | |
| nid->node->sgprivate->parents = next_pl; | |
| gf_free(pl); | |
| pl = next_pl; | |
| } else { | |
| prev_pl = pl; | |
| pl = pl->next; | |
| } | |
| } | |
| nid = nid->next; | |
| } | |
| } | |
| gf_assert(! node->sgprivate->parents); | |
| if (node->sgprivate->referencing_commands) { | |
| u32 i, cnt = gf_list_count(node->sgprivate->referencing_commands); | |
| for (i = 0; i < cnt; i++) { | |
| GF_Node **node_ptr = (GF_Node **)gf_list_get(node->sgprivate->referencing_commands, i); | |
| *node_ptr = NULL; | |
| } | |
| gf_list_del(node->sgprivate->referencing_commands); | |
| } | |
| if (node->sgprivate->referencing_protos) { | |
| u32 i, cnt = gf_list_count(node->sgprivate->referencing_protos); | |
| for (i = 0; i < cnt; i++) { | |
| GF_Proto *p = (GF_Proto *)gf_list_get(node->sgprivate->referencing_protos, i); | |
| gf_list_del_item(p->node_code, node); | |
| } | |
| gf_list_del(node->sgprivate->referencing_protos); | |
| } | |
| gf_free(node->sgprivate); | |
| gf_free(node); | |
| } | |
| GF_EXPORT | |
| u32 gf_node_get_parent_count(GF_Node *node) | |
| { | |
| u32 count = 0; | |
| GF_ParentList *nlist = node ? node->sgprivate->parents : 0; | |
| while (nlist) { | |
| count++; | |
| nlist = nlist->next; | |
| } | |
| return count; | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_node_get_parent(GF_Node *node, u32 idx) | |
| { | |
| GF_ParentList *nlist = node->sgprivate->parents; | |
| /*break cyclic graphs*/ | |
| if (node->sgprivate->scenegraph->RootNode==node) return NULL; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (node->sgprivate->scenegraph->pOwningProto && node->sgprivate->scenegraph->pOwningProto->RenderingNode==node) | |
| return NULL; | |
| #endif | |
| if (!nlist) return NULL; | |
| while (idx) { | |
| nlist = nlist->next; | |
| idx--; | |
| } | |
| return nlist ? nlist->node : NULL; | |
| } | |
| static void dirty_children(GF_Node *node) | |
| { | |
| u32 i, count; | |
| GF_FieldInfo info; | |
| if (!node) return; | |
| node->sgprivate->flags &= GF_NODE_INTERNAL_FLAGS; | |
| if (node->sgprivate->tag>=GF_NODE_RANGE_LAST_VRML) { | |
| GF_ChildNodeItem *child = ((GF_ParentNode*)node)->children; | |
| while (child) { | |
| dirty_children(child->node); | |
| child = child->next; | |
| } | |
| } else { | |
| count = gf_node_get_field_count(node); | |
| for (i=0; i<count; i++) { | |
| gf_node_get_field(node, i, &info); | |
| if (info.fieldType==GF_SG_VRML_SFNODE) dirty_children(*(GF_Node **)info.far_ptr); | |
| else if (info.fieldType==GF_SG_VRML_MFNODE) { | |
| GF_ChildNodeItem *list = *(GF_ChildNodeItem **) info.far_ptr; | |
| while (list) { | |
| dirty_children(list->node); | |
| list = list->next; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static void dirty_parents(GF_Node *node) | |
| { | |
| Bool check_root = 1; | |
| GF_ParentList *nlist; | |
| #if defined GPAC_CONFIG_ANDROID | |
| if ( !node || !node->sgprivate ) | |
| return; | |
| #else | |
| if (!node) return; | |
| #endif | |
| nlist = node->sgprivate->parents; | |
| while (nlist) { | |
| GF_Node *p = nlist->node; | |
| if (! (p->sgprivate->flags & GF_SG_CHILD_DIRTY)) { | |
| p->sgprivate->flags |= GF_SG_CHILD_DIRTY; | |
| dirty_parents(p); | |
| } | |
| check_root = 0; | |
| nlist = nlist->next; | |
| } | |
| /*propagate to parent scene graph */ | |
| #if defined GPAC_CONFIG_ANDROID | |
| if (check_root && node->sgprivate->scenegraph) { | |
| #else | |
| if (check_root) { | |
| #endif | |
| /*if root node of the scenegraph*/ | |
| if (node->sgprivate->scenegraph->NodeCallback && (node==node->sgprivate->scenegraph->RootNode) ) { | |
| node->sgprivate->scenegraph->NodeCallback(node->sgprivate->scenegraph->userpriv, GF_SG_CALLBACK_GRAPH_DIRTY, NULL, NULL); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*or if parent graph is a protoinstance but the node is not this proto*/ | |
| else if (node->sgprivate->scenegraph->pOwningProto) { | |
| GF_Node *the_node = (GF_Node *) node->sgprivate->scenegraph->pOwningProto; | |
| if (the_node != node) dirty_parents(the_node); | |
| } | |
| #endif | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_node_dirty_parent_graph(GF_Node *node) | |
| { | |
| /*if root node of the scenegraph*/ | |
| if (node->sgprivate->scenegraph->NodeCallback ) { | |
| node->sgprivate->scenegraph->NodeCallback(node->sgprivate->scenegraph->userpriv, GF_SG_CALLBACK_GRAPH_DIRTY, NULL, NULL); | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_node_dirty_set(GF_Node *node, u32 flags, Bool and_dirty_parents) | |
| { | |
| if (!node) return; | |
| if (flags) node->sgprivate->flags |= (flags & (~GF_NODE_INTERNAL_FLAGS) ); | |
| else node->sgprivate->flags |= GF_SG_NODE_DIRTY; | |
| if (and_dirty_parents) dirty_parents(node); | |
| } | |
| GF_EXPORT | |
| void gf_node_dirty_parents(GF_Node *node) | |
| { | |
| dirty_parents(node); | |
| } | |
| GF_EXPORT | |
| void gf_node_dirty_clear(GF_Node *node, u32 flag_to_remove) | |
| { | |
| if (!node) return; | |
| if (flag_to_remove) node->sgprivate->flags &= ~ (flag_to_remove & ~GF_NODE_INTERNAL_FLAGS); | |
| else node->sgprivate->flags &= GF_NODE_INTERNAL_FLAGS; | |
| } | |
| GF_EXPORT | |
| u32 gf_node_dirty_get(GF_Node *node) | |
| { | |
| if (node) return (node->sgprivate->flags & ~GF_NODE_INTERNAL_FLAGS); | |
| return 0; | |
| } | |
| GF_EXPORT | |
| void gf_node_dirty_reset(GF_Node *node, Bool reset_children) | |
| { | |
| if (!node) return; | |
| if (node->sgprivate->flags & ~GF_NODE_INTERNAL_FLAGS) { | |
| node->sgprivate->flags &= GF_NODE_INTERNAL_FLAGS; | |
| if (reset_children) { | |
| dirty_children(node); | |
| #ifndef GPAC_DISABLE_VRML | |
| } else if (node->sgprivate->tag==TAG_MPEG4_Appearance) { | |
| gf_node_dirty_reset( ((M_Appearance*)node)->material, 1); | |
| #endif | |
| } | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_node_init(GF_Node *node) | |
| { | |
| GF_SceneGraph *pSG = node->sgprivate->scenegraph; | |
| gf_assert(pSG); | |
| /*no user-defined init, consider the scenegraph is only used for parsing/encoding/decoding*/ | |
| if (!pSG->NodeCallback) return; | |
| //some broken content may trigger twice an update | |
| if (node->sgprivate->UserPrivate) return; | |
| /*internal nodes*/ | |
| #ifndef GPAC_DISABLE_VRML | |
| if (gf_sg_vrml_node_init(node)) return; | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| if (gf_svg_node_init(node)) return; | |
| #endif | |
| /*user defined init*/ | |
| pSG->NodeCallback(pSG->userpriv, GF_SG_CALLBACK_INIT, node, NULL); | |
| } | |
| void gf_node_changed_internal(GF_Node *node, GF_FieldInfo *field, Bool notify_scripts) | |
| { | |
| GF_SceneGraph *sg; | |
| if (!node) return; | |
| sg = node->sgprivate->scenegraph; | |
| gf_assert(sg); | |
| #ifndef GPAC_DISABLE_VRML | |
| /*signal changes in node to JS for MFFields*/ | |
| if (field && notify_scripts && (node->sgprivate->flags & GF_NODE_HAS_BINDING) && !gf_sg_vrml_is_sf_field(field->fieldType) ) { | |
| sg->on_node_modified(sg, node, field, NULL); | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| if (field && node->sgprivate->interact && node->sgprivate->interact->dom_evt) { | |
| GF_DOM_Event evt; | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.bubbles = 1; | |
| evt.type = GF_EVENT_ATTR_MODIFIED; | |
| evt.attr = field; | |
| evt.detail = field->fieldIndex; | |
| gf_dom_event_fire(node, &evt); | |
| } | |
| #endif | |
| /*internal nodes*/ | |
| #ifndef GPAC_DISABLE_VRML | |
| if (gf_sg_vrml_node_changed(node, field)) return; | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| if (gf_svg_node_changed(node, field)) return; | |
| #endif | |
| /*force child dirty tag*/ | |
| if (field && ( (field->fieldType==GF_SG_VRML_SFNODE) || (field->fieldType==GF_SG_VRML_MFNODE)) ) | |
| node->sgprivate->flags |= GF_SG_CHILD_DIRTY; | |
| if (sg->NodeCallback) sg->NodeCallback(sg->userpriv, GF_SG_CALLBACK_MODIFIED, node, field); | |
| } | |
| GF_EXPORT | |
| void gf_node_changed(GF_Node *node, GF_FieldInfo *field) | |
| { | |
| gf_node_changed_internal(node, field, 1); | |
| #ifndef GPAC_DISABLE_SVG | |
| /* we should avoid dispatching a DOMSubtreeModified event on insertion of time values in begin/end fields | |
| because this retriggers begin/end events and reinsertion */ | |
| if ((field == NULL || ((field->fieldIndex != TAG_SVG_ATT_begin) && (field->fieldIndex != TAG_SVG_ATT_end))) && | |
| node->sgprivate->tag >= GF_NODE_RANGE_FIRST_SVG && node->sgprivate->tag <= GF_NODE_RANGE_LAST_SVG) { | |
| GF_DOM_Event evt; | |
| evt.type = GF_EVENT_TREE_MODIFIED; | |
| evt.bubbles = 0; | |
| evt.relatedNode = node; | |
| gf_dom_event_fire(node, &evt); | |
| } | |
| #endif | |
| } | |
| void gf_node_del(GF_Node *node) | |
| { | |
| if (!node) return; | |
| if (node->sgprivate->tag==TAG_UndefinedNode) gf_node_free(node); | |
| else if (node->sgprivate->tag==TAG_DOMText) { | |
| GF_DOMText *t = (GF_DOMText *)node; | |
| if (t->textContent) gf_free(t->textContent); | |
| gf_sg_parent_reset(node); | |
| gf_node_free(node); | |
| } | |
| else if (node->sgprivate->tag==TAG_DOMUpdates) { | |
| GF_DOMUpdates *up = (GF_DOMUpdates *)node; | |
| if (up->data) gf_free(up->data); | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 i, count = gf_list_count(up->updates); | |
| for (i=0; i<count; i++) { | |
| GF_Command *com = gf_list_get(up->updates, i); | |
| gf_sg_command_del(com); | |
| } | |
| #endif | |
| gf_list_del(up->updates); | |
| gf_sg_parent_reset(node); | |
| gf_node_free(node); | |
| } | |
| else if (node->sgprivate->tag == TAG_DOMFullNode) { | |
| GF_DOMFullNode *n = (GF_DOMFullNode *)node; | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_node_delete_attributes(node); | |
| #endif | |
| if (n->name) gf_free(n->name); | |
| gf_sg_parent_reset(node); | |
| gf_node_free(node); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (node->sgprivate->tag == TAG_ProtoNode) gf_sg_proto_del_instance((GF_ProtoInstance *)node); | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (node->sgprivate->tag<=GF_NODE_RANGE_LAST_MPEG4) gf_sg_mpeg4_node_del(node); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_X3D) gf_sg_x3d_node_del(node); | |
| #endif | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_SVG) gf_svg_node_del(node); | |
| #endif | |
| else gf_node_free(node); | |
| } | |
| GF_EXPORT | |
| u32 gf_node_get_field_count(GF_Node *node) | |
| { | |
| if (!node) return 0; | |
| if (node->sgprivate->tag <= TAG_UndefinedNode) return 0; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*for both MPEG4 & X3D*/ | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_X3D) return gf_node_get_num_fields_in_mode(node, GF_SG_FIELD_CODING_ALL); | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| else if (node->sgprivate->tag >= GF_NODE_FIRST_DOM_NODE_TAG) return gf_node_get_attribute_count(node); | |
| #endif | |
| return 0; | |
| } | |
| GF_EXPORT | |
| const char *gf_node_get_class_name(GF_Node *node) | |
| { | |
| if (!node) return "null"; | |
| if (node->sgprivate->tag==TAG_UndefinedNode) return "UndefinedNode"; | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (node->sgprivate->tag==TAG_ProtoNode) return ((GF_ProtoInstance*)node)->proto_name; | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_MPEG4) return gf_sg_mpeg4_node_get_class_name(node->sgprivate->tag); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_X3D) return gf_sg_x3d_node_get_class_name(node->sgprivate->tag); | |
| #endif | |
| #endif | |
| else if (node->sgprivate->tag==TAG_DOMText) return "DOMText"; | |
| else if (node->sgprivate->tag==TAG_DOMFullNode) { | |
| char *xmlns; | |
| GF_DOMFullNode*full = (GF_DOMFullNode*)node; | |
| u32 ns = gf_sg_get_namespace_code(node->sgprivate->scenegraph, NULL); | |
| if (ns == full->ns) return full->name; | |
| xmlns = (char *) gf_sg_get_namespace_qname(node->sgprivate->scenegraph, full->ns); | |
| if (!xmlns) return full->name; | |
| snprintf(node->sgprivate->scenegraph->szNameBuffer, 99, "%s:%s", xmlns, full->name); | |
| return node->sgprivate->scenegraph->szNameBuffer; | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| else return gf_xml_get_element_name(node); | |
| #endif | |
| return "UnsupportedNode"; | |
| } | |
| #if 0 //unused | |
| u32 gf_sg_node_get_tag_by_class_name(const char *name, u32 ns) | |
| { | |
| u32 tag = TAG_UndefinedNode; | |
| /* TODO: handle name spaces */ | |
| #ifndef GPAC_DISABLE_VRML | |
| tag = gf_node_mpeg4_type_by_class_name(name); | |
| if (tag) return tag; | |
| #ifndef GPAC_DISABLE_X3D | |
| tag = gf_node_x3d_type_by_class_name(name); | |
| if (tag) return tag; | |
| #endif | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| tag = gf_xml_get_element_tag(name, ns); | |
| if (tag != TAG_UndefinedNode) return tag; | |
| #endif | |
| return tag; | |
| } | |
| #endif | |
| GF_EXPORT | |
| GF_Node *gf_node_new(GF_SceneGraph *inScene, u32 tag) | |
| { | |
| GF_Node *node; | |
| // if (!inScene) return NULL; | |
| /*cannot create proto this way*/ | |
| if (tag==TAG_ProtoNode) return NULL; | |
| else if (tag==TAG_UndefinedNode) node = gf_sg_new_base_node(); | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (tag <= GF_NODE_RANGE_LAST_MPEG4) node = gf_sg_mpeg4_node_new(tag); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (tag <= GF_NODE_RANGE_LAST_X3D) node = gf_sg_x3d_node_new(tag); | |
| #endif | |
| #endif | |
| else if (tag == TAG_DOMText) { | |
| GF_DOMText *n; | |
| GF_SAFEALLOC(n, GF_DOMText); | |
| if (!n) return NULL; | |
| node = (GF_Node*)n; | |
| gf_node_setup(node, TAG_DOMText); | |
| } | |
| else if (tag == TAG_DOMFullNode) { | |
| GF_DOMFullNode*n; | |
| GF_SAFEALLOC(n, GF_DOMFullNode); | |
| if (!n) return NULL; | |
| node = (GF_Node*)n; | |
| gf_node_setup(node, TAG_DOMFullNode); | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| else if (tag >= GF_NODE_RANGE_FIRST_SVG && tag <= GF_NODE_RANGE_LAST_SVG) node = (GF_Node *) gf_svg_create_node(tag); | |
| #endif | |
| else node = NULL; | |
| if (node) node->sgprivate->scenegraph = inScene; | |
| /*script is inited as soon as created since fields are dynamically added*/ | |
| #ifndef GPAC_DISABLE_VRML | |
| switch (tag) { | |
| case TAG_MPEG4_Script: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Script: | |
| #endif | |
| gf_sg_script_init(node); | |
| break; | |
| } | |
| #endif | |
| return node; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_get_field(GF_Node *node, u32 FieldIndex, GF_FieldInfo *info) | |
| { | |
| if (!node || !info) return GF_BAD_PARAM; | |
| memset(info, 0, sizeof(GF_FieldInfo)); | |
| info->fieldIndex = FieldIndex; | |
| if (node->sgprivate->tag==TAG_UndefinedNode) return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (node->sgprivate->tag == TAG_ProtoNode) return gf_sg_proto_get_field(NULL, node, info); | |
| else if (node->sgprivate->tag == TAG_MPEG4_Script) | |
| return gf_sg_script_get_field(node, info); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag == TAG_X3D_Script) | |
| return gf_sg_script_get_field(node, info); | |
| #endif | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_MPEG4) return gf_sg_mpeg4_node_get_field(node, info); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_X3D) return gf_sg_x3d_node_get_field(node, info); | |
| #endif | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| else if (node->sgprivate->tag >= GF_NODE_FIRST_DOM_NODE_TAG) return gf_node_get_attribute_info(node, info); | |
| #endif | |
| return GF_NOT_SUPPORTED; | |
| } | |
| u32 gf_node_get_num_instances(GF_Node *node) | |
| { | |
| return node ? node->sgprivate->num_instances : 0; | |
| } | |
| static GF_Err gf_node_get_field_by_name_enum(GF_Node *node, char *name, GF_FieldInfo *field) | |
| { | |
| u32 i, count; | |
| gf_assert(node); | |
| count = gf_node_get_field_count(node); | |
| memset(field, 0, sizeof(GF_FieldInfo)); | |
| for (i=0; i<count; i++) { | |
| gf_node_get_field(node, i, field); | |
| if (!strcmp(field->name, name)) return GF_OK; | |
| } | |
| return GF_BAD_PARAM; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_node_get_field_by_name(GF_Node *node, char *name, GF_FieldInfo *field) | |
| { | |
| s32 res = -1; | |
| if (!node || !node->sgprivate || node->sgprivate->tag==TAG_UndefinedNode) return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (node->sgprivate->tag == TAG_ProtoNode) { | |
| res = gf_sg_proto_get_field_index_by_name(NULL, node, name); | |
| } | |
| else if (node->sgprivate->tag == TAG_MPEG4_Script) | |
| return gf_node_get_field_by_name_enum(node, name, field); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag == TAG_X3D_Script) | |
| return gf_node_get_field_by_name_enum(node, name, field); | |
| #endif | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_MPEG4) res = gf_sg_mpeg4_node_get_field_index_by_name(node, name); | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (node->sgprivate->tag <= GF_NODE_RANGE_LAST_X3D) res = gf_sg_x3d_node_get_field_index_by_name(node, name); | |
| #endif | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| else if (node->sgprivate->tag >= GF_NODE_FIRST_DOM_NODE_TAG) return gf_node_get_attribute_by_name(node, name, 0, 1, 0, field); | |
| #endif | |
| if (res==-1) return GF_BAD_PARAM; | |
| return gf_node_get_field(node, (u32) res, field); | |
| } | |
| static char log_node_name[2+16+1]; | |
| const char *gf_node_get_log_name(GF_Node *anim) | |
| { | |
| const char *name; | |
| if (!anim) return ""; | |
| name = gf_node_get_name(anim); | |
| if (name) return name; | |
| else { | |
| sprintf(log_node_name, "%p", anim); | |
| return log_node_name; | |
| } | |
| } | |
| static GF_Err gf_node_deactivate_ex(GF_Node *node, u32 depth) | |
| { | |
| #ifdef GPAC_DISABLE_SVG | |
| return GF_NOT_SUPPORTED; | |
| #else | |
| GF_ChildNodeItem *item; | |
| Bool was_deactivated; | |
| /*avoid stack overflow on cyclic/malformed scene graphs*/ | |
| if (depth > 255) return GF_OK; | |
| if (node->sgprivate->tag<GF_NODE_FIRST_DOM_NODE_TAG) return GF_BAD_PARAM; | |
| was_deactivated = (node->sgprivate->flags & GF_NODE_IS_DEACTIVATED) ? GF_TRUE : GF_FALSE; | |
| if (!was_deactivated) { | |
| node->sgprivate->flags |= GF_NODE_IS_DEACTIVATED; | |
| /*deactivate anmiations*/ | |
| if (gf_svg_is_timing_tag(node->sgprivate->tag)) { | |
| SVGTimedAnimBaseElement *timed = (SVGTimedAnimBaseElement*)node; | |
| if (timed && timed->timingp && gf_list_del_item(node->sgprivate->scenegraph->smil_timed_elements, timed->timingp->runtime)>=0) { | |
| if (timed->timingp->runtime->evaluate) { | |
| timed->timingp->runtime->evaluate(timed->timingp->runtime, 0, SMIL_TIMING_EVAL_DEACTIVATE); | |
| } | |
| } | |
| } | |
| /*TODO unregister all listeners*/ | |
| } | |
| if (was_deactivated) return GF_OK; | |
| /*and deactivate children*/ | |
| item = ((GF_ParentNode*)node)->children; | |
| while (item) { | |
| gf_node_deactivate_ex(item->node, depth+1); | |
| item = item->next; | |
| } | |
| return GF_OK; | |
| #endif | |
| } | |
| GF_Err gf_node_deactivate(GF_Node *node) | |
| { | |
| GF_Err e = gf_node_deactivate_ex(node, 0); | |
| gf_node_changed(node, NULL); | |
| return e; | |
| } | |
| static u32 gf_node_activate_ex(GF_Node *node, u32 depth) | |
| { | |
| #ifdef GPAC_DISABLE_SVG | |
| return 0; | |
| #else | |
| u32 ret = 0; | |
| GF_ChildNodeItem *item; | |
| /*avoid stack overflow on cyclic/malformed scene graphs*/ | |
| if (depth > 255) return 0; | |
| if (node->sgprivate->tag<GF_NODE_FIRST_DOM_NODE_TAG) return 0; | |
| if (node->sgprivate->flags & GF_NODE_IS_DEACTIVATED) { | |
| node->sgprivate->flags &= ~GF_NODE_IS_DEACTIVATED; | |
| ret ++; | |
| /*deactivate anmiations*/ | |
| if (gf_svg_is_timing_tag(node->sgprivate->tag)) { | |
| SVGTimedAnimBaseElement *timed = (SVGTimedAnimBaseElement*)node; | |
| gf_list_add(node->sgprivate->scenegraph->smil_timed_elements, timed->timingp->runtime); | |
| node->sgprivate->flags &= ~GF_NODE_IS_DEACTIVATED; | |
| if (timed->timingp->runtime->evaluate) { | |
| timed->timingp->runtime->evaluate(timed->timingp->runtime, 0, SMIL_TIMING_EVAL_ACTIVATE); | |
| } | |
| } | |
| /*TODO register all listeners*/ | |
| } | |
| /*and deactivate children*/ | |
| item = ((GF_ParentNode*)node)->children; | |
| while (item) { | |
| ret += gf_node_activate_ex(item->node, depth+1); | |
| item = item->next; | |
| } | |
| return ret; | |
| #endif | |
| } | |
| GF_Err gf_node_activate(GF_Node *node) | |
| { | |
| if (!node) return GF_BAD_PARAM; | |
| if (gf_node_activate_ex(node, 0)) | |
| gf_node_changed(node, NULL); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Node *gf_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *id, Bool deep) | |
| { | |
| if (!orig) return NULL; | |
| if (orig->sgprivate->tag < GF_NODE_RANGE_LAST_VRML) { | |
| #ifndef GPAC_DISABLE_VRML | |
| /*deep clone is always true for VRML*/ | |
| return gf_vrml_node_clone(inScene, orig, cloned_parent, id); | |
| #endif | |
| } else if (orig->sgprivate->tag == TAG_DOMUpdates) { | |
| /*TODO*/ | |
| return NULL; | |
| } else { | |
| #ifndef GPAC_DISABLE_SVG | |
| return gf_sg_xml_node_clone(inScene, orig, cloned_parent, id, deep); | |
| #endif | |
| } | |
| return NULL; | |
| } | |
| GF_NamespaceType gf_xml_get_namespace_id(char *name) | |
| { | |
| if (!strcmp(name, "http://www.w3.org/XML/1998/namespace")) return GF_XMLNS_XML; | |
| else if (!strcmp(name, "http://www.w3.org/2001/xml-events")) return GF_XMLNS_XMLEV; | |
| else if (!strcmp(name, "http://www.w3.org/1999/xlink")) return GF_XMLNS_XLINK; | |
| else if (!strcmp(name, "http://www.w3.org/2000/svg")) return GF_XMLNS_SVG; | |
| else if (!strcmp(name, "urn:mpeg:mpeg4:LASeR:2005")) return GF_XMLNS_LASER; | |
| else if (!strcmp(name, "http://www.w3.org/ns/xbl")) return GF_XMLNS_XBL; | |
| else if (!strcmp(name, "https://gpac.io/svg-extensions")) return GF_XMLNS_SVG_GPAC_EXTENSION; | |
| return GF_XMLNS_UNDEFINED; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sg_add_namespace(GF_SceneGraph *sg, char *name, char *qname) | |
| { | |
| u32 id; | |
| GF_XMLNS *ns; | |
| if (!name) return GF_BAD_PARAM; | |
| id = gf_xml_get_namespace_id(name); | |
| if (!sg->ns) sg->ns = gf_list_new(); | |
| GF_SAFEALLOC(ns, GF_XMLNS); | |
| if (!ns) return GF_OUT_OF_MEM; | |
| ns->xmlns_id = id ? id : gf_crc_32(name, (u32) strlen(name)); | |
| ns->name = gf_strdup(name); | |
| ns->qname = qname ? gf_strdup(qname) : NULL; | |
| return gf_list_insert(sg->ns, ns, 0); | |
| } | |
| GF_Err gf_sg_remove_namespace(GF_SceneGraph *sg, char *ns_name, char *q_name) | |
| { | |
| u32 i, count; | |
| if (!ns_name) return GF_OK; | |
| count = sg->ns ? gf_list_count(sg->ns) : 0; | |
| for (i=0; i<count; i++) { | |
| Bool ok=0; | |
| GF_XMLNS *ns = gf_list_get(sg->ns, i); | |
| if (!q_name && !ns->qname) | |
| ok = 1; | |
| else if (q_name && ns->qname && !strcmp(ns->qname, q_name) ) | |
| ok = 1; | |
| if (ok && ns->name && !strcmp(ns->name, ns_name)) { | |
| gf_list_rem(sg->ns, i); | |
| gf_free(ns->name); | |
| if (ns->qname) gf_free(ns->qname); | |
| gf_free(ns); | |
| return GF_OK; | |
| } | |
| } | |
| return GF_OK; | |
| } | |
| GF_NamespaceType gf_sg_get_namespace_code(GF_SceneGraph *sg, char *qname) | |
| { | |
| u32 i, count; | |
| count = sg->ns ? gf_list_count(sg->ns) : 0; | |
| for (i=0; i<count; i++) { | |
| GF_XMLNS *ns = gf_list_get(sg->ns, i); | |
| if (!ns->qname && !qname) | |
| return ns->xmlns_id; | |
| if (ns->qname && qname && !strcmp(ns->qname, qname)) | |
| return ns->xmlns_id; | |
| } | |
| if (qname) { | |
| if (!strcmp(qname, "xml")) return GF_XMLNS_XML; | |
| /*we could also add the basic namespaces in case this has been forgotten ?*/ | |
| } | |
| return GF_XMLNS_UNDEFINED; | |
| } | |
| GF_NamespaceType gf_sg_get_namespace_code_from_name(GF_SceneGraph *sg, char *name) | |
| { | |
| u32 i, count; | |
| count = sg->ns ? gf_list_count(sg->ns) : 0; | |
| for (i=0; i<count; i++) { | |
| GF_XMLNS *ns = gf_list_get(sg->ns, i); | |
| if (ns->name && name && !strcmp(ns->name, name)) | |
| return ns->xmlns_id; | |
| if (ns->qname && name && !strcmp(ns->qname, name)) | |
| return ns->xmlns_id; | |
| if (!ns->name && !name) | |
| return ns->xmlns_id; | |
| } | |
| return GF_XMLNS_UNDEFINED; | |
| } | |
| const char *gf_sg_get_namespace_qname(GF_SceneGraph *sg, GF_NamespaceType xmlns_id) | |
| { | |
| u32 i, count; | |
| count = sg->ns ? gf_list_count(sg->ns) : 0; | |
| for (i=0; i<count; i++) { | |
| GF_XMLNS *ns = gf_list_get(sg->ns, i); | |
| if (ns->xmlns_id == xmlns_id) | |
| return ns->qname; | |
| } | |
| if (xmlns_id==GF_XMLNS_XML) return "xml"; | |
| return NULL; | |
| } | |
| const char *gf_sg_get_namespace(GF_SceneGraph *sg, GF_NamespaceType xmlns_id) | |
| { | |
| u32 i, count; | |
| if (!sg) return NULL; | |
| count = sg->ns ? gf_list_count(sg->ns) : 0; | |
| for (i=0; i<count; i++) { | |
| GF_XMLNS *ns = gf_list_get(sg->ns, i); | |
| if (ns->xmlns_id == xmlns_id) | |
| return ns->name; | |
| } | |
| return NULL; | |
| } | |
| #if 0 //unused | |
| char *gf_node_dump_attribute(GF_Node *n, GF_FieldInfo *info) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| if (gf_node_get_tag(n) >= GF_NODE_FIRST_DOM_NODE_TAG) { | |
| return gf_svg_dump_attribute(n, info); | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| return gf_node_vrml_dump_attribute(n, info); | |
| #else | |
| return NULL; | |
| #endif | |
| } | |
| #endif | |
| /*this is not a NodeReplace, thus only the given container is updated - pos is 0-based*/ | |
| GF_EXPORT | |
| GF_Err gf_node_replace_child(GF_Node *node, GF_ChildNodeItem **container, s32 pos, GF_Node *newNode) | |
| { | |
| GF_ChildNodeItem *child, *prev; | |
| if (!container || !*container) return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 tag; | |
| #endif | |
| u32 cur_pos = 0; | |
| child = *container; | |
| prev = NULL; | |
| while (child->next) { | |
| if ((pos<0) || (cur_pos!=(u32)pos)) { | |
| prev = child; | |
| child = child->next; | |
| cur_pos++; | |
| continue; | |
| } | |
| break; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| tag = child->node->sgprivate->tag; | |
| #endif | |
| gf_node_unregister(child->node, node); | |
| if (newNode) { | |
| child->node = newNode; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (tag==TAG_MPEG4_ColorTransform) | |
| node->sgprivate->flags |= GF_SG_VRML_COLOR_DIRTY; | |
| #endif | |
| } else { | |
| if (prev) prev->next = child->next; | |
| else *container = child->next; | |
| gf_free(child); | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| Bool gf_node_parent_of(GF_Node *node, GF_Node *target) | |
| { | |
| u32 i, count; | |
| GF_FieldInfo info; | |
| if (!node) return 0; | |
| if (node==target) return 1; | |
| if (node->sgprivate->tag>=GF_NODE_RANGE_LAST_VRML) { | |
| GF_ChildNodeItem *child = ((GF_ParentNode*)node)->children; | |
| while (child) { | |
| if (gf_node_parent_of(child->node, target)) return 1; | |
| child = child->next; | |
| } | |
| } else { | |
| count = gf_node_get_field_count(node); | |
| for (i=0; i<count; i++) { | |
| gf_node_get_field(node, i, &info); | |
| if (info.fieldType==GF_SG_VRML_SFNODE) { | |
| if (gf_node_parent_of(*(GF_Node **)info.far_ptr, target)) return 1; | |
| } | |
| else if (info.fieldType==GF_SG_VRML_MFNODE) { | |
| GF_ChildNodeItem *list = *(GF_ChildNodeItem **) info.far_ptr; | |
| while (list) { | |
| if (gf_node_parent_of(list->node, target)) return 1; | |
| list = list->next; | |
| } | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| GF_SceneGraph *gf_sg_get_parent(GF_SceneGraph *scene) | |
| { | |
| return scene ? scene->parent_scene : NULL; | |
| } | |
| #include <gpac/xml.h> | |
| static GF_Err gf_sg_load_dom_node(GF_SceneGraph *document, GF_XMLNode *n, GF_DOMFullNode *par) | |
| { | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SVG) | |
| u32 i, count; | |
| GF_DOMFullAttribute *prev = NULL; | |
| GF_DOMFullNode *node; | |
| if (!n) return GF_OK; | |
| if (!par && document->RootNode) { | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| /*construct text / cdata node*/ | |
| if (n->type != GF_XML_NODE_TYPE) { | |
| u32 len; | |
| GF_DOMText *txt; | |
| /*basic check, remove all empty text nodes*/ | |
| len = (u32) strlen(n->name); | |
| for (i=0; i<len; i++) { | |
| if (!strchr(" \n\r\t", n->name[i])) break; | |
| } | |
| if (i==len) return GF_OK; | |
| txt = gf_dom_add_text_node((GF_Node *)par, gf_strdup(n->name) ); | |
| txt->type = (n->type==GF_XML_CDATA_TYPE) ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR; | |
| return GF_OK; | |
| } | |
| /*construct DOM node*/ | |
| node = (GF_DOMFullNode *) gf_node_new(document, TAG_DOMFullNode); | |
| node->name = gf_strdup(n->name); | |
| if (n->ns) | |
| node->ns = gf_sg_get_namespace_code(document, n->ns); | |
| count = gf_list_count(n->attributes); | |
| for (i=0; i<count; i++) { | |
| GF_XMLAttribute *src_att = gf_list_get(n->attributes, i); | |
| /* special case for 'xml:id' to be parsed as an ID | |
| NOTE: we do not test for the 'id' attribute because without DTD we are not sure that it's an ID */ | |
| if (!stricmp(src_att->name, "xml:id")) { | |
| u32 id = gf_sg_get_max_node_id(document) + 1; | |
| gf_node_set_id((GF_Node *)node, id, src_att->value); | |
| } else { | |
| GF_DOMFullAttribute *att; | |
| GF_SAFEALLOC(att, GF_DOMFullAttribute); | |
| if (!att) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[XHR] Fail to allocate DOM attribute\n")); | |
| continue; | |
| } | |
| att->tag = TAG_DOM_ATT_any; | |
| att->name = gf_strdup(src_att->name); | |
| att->data_type = (u16) DOM_String_datatype; | |
| att->data = gf_svg_create_attribute_value(att->data_type); | |
| *((char **)att->data) = gf_strdup(src_att->value); | |
| if (prev) prev->next = (GF_DOMAttribute*)att; | |
| else node->attributes = (GF_DOMAttribute*)att; | |
| prev = att; | |
| } | |
| } | |
| gf_node_register((GF_Node*)node, (GF_Node*)par); | |
| if (par) { | |
| gf_node_list_add_child(&par->children, (GF_Node*)node); | |
| } else { | |
| document->RootNode = (GF_Node*)node; | |
| } | |
| count = gf_list_count(n->content); | |
| for (i=0; i<count; i++) { | |
| GF_XMLNode *child = gf_list_get(n->content, i); | |
| GF_Err e = gf_sg_load_dom_node(document, child, node); | |
| if (e) return e; | |
| } | |
| return GF_OK; | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| GF_Err gf_sg_init_from_xml_node(GF_SceneGraph *document, GF_DOMXMLNODE root_node) | |
| { | |
| return gf_sg_load_dom_node(document, root_node, NULL); | |
| } | |
| void gf_sg_set_destroy_cookie(GF_SceneGraph *sg, Bool *cookie) | |
| { | |
| sg->destroy_cookie = cookie; | |
| } |