static void view_translate_x(GF_Compositor *compositor, GF_Camera *cam, Fixed dx) { SFVec3f v; if (!dx) return; if (cam->jumping) dx *= JUMP_SCALE_FACTOR; v = gf_vec_scale(camera_get_right_dir(cam), dx); gf_vec_add(cam->target, cam->target, v); gf_vec_add(cam->position, cam->position, v); camera_changed(compositor, cam); }
static void view_translate_y(GF_Compositor *compositor, GF_Camera *cam, Fixed dy) { SFVec3f v; if (!dy) return; if (cam->jumping) dy *= JUMP_SCALE_FACTOR; v = gf_vec_scale(cam->up, dy); gf_vec_add(cam->target, cam->target, v); gf_vec_add(cam->position, cam->position, v); camera_changed(compositor, cam); }
static void view_translate_z(GF_Compositor *compositor, GF_Camera *cam, Fixed dz) { SFVec3f v; if (!dz) return; if (cam->jumping) dz *= JUMP_SCALE_FACTOR; dz = gf_mulfix(dz, cam->speed); v = gf_vec_scale(camera_get_target_dir(cam), dz); gf_vec_add(cam->target, cam->target, v); gf_vec_add(cam->position, cam->position, v); camera_changed(compositor, cam); }
void camera_set_vectors(GF_Camera *cam, SFVec3f pos, SFRotation ori, Fixed fov) { Fixed sin_a, cos_a, icos_a, tmp; cam->fieldOfView = fov; cam->last_pos = cam->position; cam->position = pos; /*compute up & target vectors in local system*/ sin_a = gf_sin(ori.q); cos_a = gf_cos(ori.q); icos_a = FIX_ONE - cos_a; tmp = gf_mulfix(icos_a, ori.z); cam->target.x = gf_mulfix(ori.x, tmp) + gf_mulfix(sin_a, ori.y); cam->target.y = gf_mulfix(ori.y, tmp) - gf_mulfix(sin_a, ori.x); cam->target.z = gf_mulfix(ori.z, tmp) + cos_a; gf_vec_norm(&cam->target); cam->target = gf_vec_scale(cam->target, -cam->vp_dist); gf_vec_add(cam->target, cam->target, pos); tmp = gf_mulfix(icos_a, ori.y); cam->up.x = gf_mulfix(ori.x, tmp) - gf_mulfix(sin_a, ori.z); cam->up.y = gf_mulfix(ori.y, tmp) + cos_a; cam->up.z = gf_mulfix(ori.z, tmp) + gf_mulfix(sin_a, ori.x); gf_vec_norm(&cam->up); cam->flags |= CAM_IS_DIRTY; }
void RenderVisibilitySensor(GF_Node *node, void *rs, Bool is_destroy) { RenderEffect3D *eff = (RenderEffect3D *)rs; M_VisibilitySensor *vs = (M_VisibilitySensor *)node; if (is_destroy || !vs->enabled) return; if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) { /*work with twice bigger bbox to get sure we're notify when culled out*/ gf_vec_add(eff->bbox.max_edge, vs->center, vs->size); gf_vec_diff(eff->bbox.min_edge, vs->center, vs->size); gf_bbox_refresh(&eff->bbox); } else if (eff->traversing_mode==TRAVERSE_SORT) { Bool visible; u32 cull_flag; GF_BBox bbox; SFVec3f s; s = gf_vec_scale(vs->size, FIX_ONE/2); /*cull with normal bbox*/ gf_vec_add(bbox.max_edge, vs->center, s); gf_vec_diff(bbox.min_edge, vs->center, s); gf_bbox_refresh(&bbox); cull_flag = eff->cull_flag; eff->cull_flag = CULL_INTERSECTS; visible = node_cull(eff, &bbox, 0); eff->cull_flag = cull_flag; if (visible && !vs->isActive) { vs->isActive = 1; gf_node_event_out_str(node, "isActive"); vs->enterTime = gf_node_get_scene_time(node); gf_node_event_out_str(node, "enterTime"); } else if (!visible && vs->isActive) { vs->isActive = 0; gf_node_event_out_str(node, "isActive"); vs->exitTime = gf_node_get_scene_time(node); gf_node_event_out_str(node, "exitTime"); } } }
void TraverseVisibilitySensor(GF_Node *node, void *rs, Bool is_destroy) { GF_TraverseState *tr_state = (GF_TraverseState *)rs; M_VisibilitySensor *vs = (M_VisibilitySensor *)node; if (is_destroy || !vs->enabled) return; if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) { /*work with twice bigger bbox to get sure we're notify when culled out*/ gf_vec_add(tr_state->bbox.max_edge, vs->center, vs->size); gf_vec_diff(tr_state->bbox.min_edge, vs->center, vs->size); gf_bbox_refresh(&tr_state->bbox); } else if (tr_state->traversing_mode==TRAVERSE_SORT) { Bool visible; u32 cull_flag; GF_BBox bbox; SFVec3f s; s = gf_vec_scale(vs->size, FIX_ONE/2); /*cull with normal bbox*/ gf_vec_add(bbox.max_edge, vs->center, s); gf_vec_diff(bbox.min_edge, vs->center, s); gf_bbox_refresh(&bbox); cull_flag = tr_state->cull_flag; tr_state->cull_flag = CULL_INTERSECTS; visible = visual_3d_node_cull(tr_state, &bbox, 0); tr_state->cull_flag = cull_flag; if (visible && !vs->isActive) { vs->isActive = 1; gf_node_event_out(node, 5/*"isActive"*/); vs->enterTime = gf_node_get_scene_time(node); gf_node_event_out(node, 3/*"enterTime"*/); } else if (!visible && vs->isActive) { vs->isActive = 0; gf_node_event_out(node, 5/*"isActive"*/); vs->exitTime = gf_node_get_scene_time(node); gf_node_event_out(node, 4/*"exitTime"*/); } } }
static void view_roll(GF_Compositor *compositor, GF_Camera *cam, Fixed dd) { GF_Matrix mx; SFVec3f delta; if (!dd) return; gf_vec_add(delta, cam->target, cam->up); gf_mx_rotation_matrix(&mx, cam->target, camera_get_pos_dir(cam), dd); gf_mx_apply_vec(&mx, &delta); gf_vec_diff(cam->up, delta, cam->target); gf_vec_norm(&cam->up); camera_changed(compositor, cam); }
static void TraverseSpotLight(GF_Node *n, void *rs, Bool is_destroy) { M_SpotLight *sl = (M_SpotLight *)n; GF_TraverseState *tr_state = (GF_TraverseState *) rs; if (is_destroy) { Bool *vis = gf_node_get_private(n); gf_free(vis); return; } if (!sl->on) return; /*store local bounds for culling*/ if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) { GF_BBox b; SFVec3f size; Bool *visible = gf_node_get_private(n); size.x = size.y = size.z = sl->radius; gf_vec_add(b.max_edge, sl->location, size); gf_vec_diff(b.min_edge, sl->location, size); gf_bbox_refresh(&b); *visible = visual_3d_node_cull(tr_state, &b, 0); /*if visible, disable culling on our parent branch - this is not very efficient but we only store one bound per grouping node, and we don't want the lights to interfere with it*/ if (*visible) tr_state->disable_cull = 1; return; } else if (tr_state->traversing_mode == TRAVERSE_LIGHTING) { Bool *visible = gf_node_get_private(n); if (*visible) { visual_3d_matrix_push(tr_state->visual); visual_3d_matrix_add(tr_state->visual, tr_state->model_matrix.m); visual_3d_add_spot_light(tr_state->visual, sl->ambientIntensity, sl->attenuation, sl->beamWidth, sl->color, sl->cutOffAngle, sl->direction, sl->intensity, sl->location); visual_3d_matrix_pop(tr_state->visual); } } }
Bool compositor_get_2d_plane_intersection(GF_Ray *ray, SFVec3f *res) { GF_Plane p; Fixed t, t2; if (!ray->dir.x && !ray->dir.y) { res->x = ray->orig.x; res->y = ray->orig.y; res->z = 0; return 1; } p.normal.x = p.normal.y = 0; p.normal.z = FIX_ONE; p.d = 0; t2 = gf_vec_dot(p.normal, ray->dir); if (t2 == 0) return 0; t = - gf_divfix(gf_vec_dot(p.normal, ray->orig) + p.d, t2); if (t<0) return 0; *res = gf_vec_scale(ray->dir, t); gf_vec_add(*res, ray->orig, *res); return 1; }
static void TraversePointLight(GF_Node *n, void *rs, Bool is_destroy) { M_PointLight *pl = (M_PointLight *)n; GF_TraverseState *tr_state = (GF_TraverseState *) rs; if (is_destroy) { Bool *vis = gf_node_get_private(n); gf_free(vis); return; } if (!pl->on) return; /*store local bounds for culling*/ if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) { SFVec3f size; GF_BBox b; Bool *visible = gf_node_get_private(n); size.x = size.y = size.z = pl->radius; gf_vec_add(b.max_edge, pl->location, size); gf_vec_diff(b.min_edge, pl->location, size); gf_bbox_refresh(&b); *visible = visual_3d_node_cull(tr_state, &b, 0); /*if visible, disable culling on our parent branch*/ if (*visible) tr_state->disable_cull = 1; return; } else if (tr_state->traversing_mode == TRAVERSE_LIGHTING) { Bool *visible = gf_node_get_private(n); if (*visible) { visual_3d_matrix_push(tr_state->visual); visual_3d_matrix_add(tr_state->visual, tr_state->model_matrix.m); visual_3d_add_point_light(tr_state->visual, pl->ambientIntensity, pl->attenuation, pl->color, pl->intensity, pl->location); visual_3d_matrix_pop(tr_state->visual); } } }
void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed nominal_view_distance, Fixed view_distance_offset, u32 camera_layout) { Fixed vlen, h, w, ar; SFVec3f corner, center; GF_Matrix post_model_view; if (! (cam->flags & CAM_IS_DIRTY)) return; ar = gf_divfix(cam->width, cam->height); gf_mx_init(post_model_view); if (cam->is_3D) { /*setup perspective*/ if (camera_layout==GF_3D_CAMERA_OFFAXIS) { Fixed left, right, top, bottom, shift, wd2, ndfl, viewing_distance; SFVec3f eye, pos, tar, disp; viewing_distance = nominal_view_distance; wd2 = gf_mulfix(cam->z_near, gf_tan(cam->fieldOfView/2)); ndfl = gf_divfix(cam->z_near, viewing_distance); /*compute h displacement*/ shift = gf_mulfix(horizontal_shift, ndfl); top = wd2; bottom = -top; left = -gf_mulfix(ar, wd2) - shift; right = gf_mulfix(ar, wd2) - shift; gf_mx_init(cam->projection); cam->projection.m[0] = gf_divfix(2*cam->z_near, (right-left)); cam->projection.m[5] = gf_divfix(2*cam->z_near, (top-bottom)); cam->projection.m[8] = gf_divfix(right+left, right-left); cam->projection.m[9] = gf_divfix(top+bottom, top-bottom); cam->projection.m[10] = gf_divfix(cam->z_far+cam->z_near, cam->z_near-cam->z_far); cam->projection.m[11] = -FIX_ONE; cam->projection.m[14] = 2*gf_muldiv(cam->z_near, cam->z_far, cam->z_near-cam->z_far); cam->projection.m[15] = 0; gf_vec_diff(eye, cam->target, cam->position); gf_vec_norm(&eye); disp = gf_vec_cross(eye, cam->up); gf_vec_norm(&disp); gf_vec_diff(center, cam->world_bbox.center, cam->position); vlen = gf_vec_len(center); shift = gf_mulfix(horizontal_shift, gf_divfix(vlen, viewing_distance)); pos = gf_vec_scale(disp, shift); gf_vec_add(pos, pos, cam->position); gf_vec_add(tar, pos, eye); /*setup modelview*/ gf_mx_lookat(&cam->modelview, pos, tar, cam->up); } else { gf_mx_perspective(&cam->projection, cam->fieldOfView, ar, cam->z_near, cam->z_far); /*setup modelview*/ gf_mx_lookat(&cam->modelview, cam->position, cam->target, cam->up); } if (!center_coords) { gf_mx_add_scale(&post_model_view, FIX_ONE, -FIX_ONE, FIX_ONE); gf_mx_add_translation(&post_model_view, -cam->width / 2, -cam->height / 2, 0); } /*compute center and radius - CHECK ME!*/ vlen = cam->z_far - cam->z_near; h = gf_mulfix(vlen , gf_tan(cam->fieldOfView / 2)); w = gf_mulfix(h, ar); center.x = 0; center.y = 0; center.z = cam->z_near + vlen / 2; corner.x = w; corner.y = h; corner.z = vlen; gf_vec_diff(corner, corner, center); cam->radius = gf_vec_len(corner); gf_vec_diff(cam->center, cam->target, cam->position); gf_vec_norm(&cam->center); cam->center = gf_vec_scale(cam->center, cam->z_near + vlen/2); gf_vec_add(cam->center, cam->center, cam->position); } else { GF_BBox b; Fixed hw, hh; hw = cam->width / 2; hh = cam->height / 2; cam->z_near = INT2FIX(NEAR_PLANE_2D); cam->z_far = INT2FIX(FAR_PLANE_2D); /*setup ortho*/ gf_mx_ortho(&cam->projection, -hw, hw, -hh, hh, cam->z_near, cam->z_far); /*setup modelview*/ gf_mx_init(cam->modelview); #ifdef FORCE_CAMERA_3D if (! (cam->flags & CAM_NO_LOOKAT)) gf_mx_lookat(&cam->modelview, cam->position, cam->target, cam->up); #endif if (!center_coords) { gf_mx_add_scale(&post_model_view, FIX_ONE, -FIX_ONE, FIX_ONE); gf_mx_add_translation(&post_model_view, -hw, -hh, 0); } if (user_transform) { #ifdef FORCE_CAMERA_3D if (! (cam->flags & CAM_NO_LOOKAT)) { GF_Matrix mx; gf_mx_from_mx2d(&mx, user_transform); mx.m[10] = mx.m[0]; gf_mx_add_matrix(&post_model_view, &mx); } else #endif gf_mx_add_matrix_2d(&post_model_view, user_transform); } if (cam->end_zoom != FIX_ONE) gf_mx_add_scale(&post_model_view, cam->end_zoom, cam->end_zoom, cam->end_zoom); if (cam->flags & CAM_HAS_VIEWPORT) gf_mx_add_matrix(&post_model_view, &cam->viewport); /*compute center & radius*/ b.max_edge.x = hw; b.max_edge.y = hh; b.min_edge.x = -hw; b.min_edge.y = -hh; b.min_edge.z = b.max_edge.z = (cam->z_near+cam->z_far) / 2; gf_bbox_refresh(&b); cam->center = b.center; cam->radius = b.radius; if (camera_layout==GF_3D_CAMERA_OFFAXIS) camera_layout=GF_3D_CAMERA_LINEAR; } if (camera_layout == GF_3D_CAMERA_CIRCULAR) { GF_Matrix mx; Fixed viewing_distance = nominal_view_distance; SFVec3f pos, target; Fixed angle; gf_vec_diff(center, cam->world_bbox.center, cam->position); vlen = gf_vec_len(center); vlen += gf_mulfix(view_distance_offset, gf_divfix(vlen, nominal_view_distance)); gf_vec_diff(pos, cam->target, cam->position); gf_vec_norm(&pos); pos = gf_vec_scale(pos, vlen); gf_vec_add(target, pos, cam->position); gf_mx_init(mx); gf_mx_add_translation(&mx, target.x, target.y, target.z); angle = gf_atan2(horizontal_shift, viewing_distance); gf_mx_add_rotation(&mx, angle, cam->up.x, cam->up.y, cam->up.z); gf_mx_add_translation(&mx, -target.x, -target.y, -target.z); pos = cam->position; gf_mx_apply_vec(&mx, &pos); gf_mx_lookat(&cam->modelview, pos, target, cam->up); } else if (camera_layout == GF_3D_CAMERA_LINEAR) { Fixed viewing_distance = nominal_view_distance + view_distance_offset; GF_Vec eye, disp, pos, tar; gf_vec_diff(center, cam->world_bbox.center, cam->position); vlen = gf_vec_len(center); vlen += gf_mulfix(view_distance_offset, gf_divfix(vlen, nominal_view_distance)); gf_vec_diff(eye, cam->target, cam->position); gf_vec_norm(&eye); tar = gf_vec_scale(eye, vlen); gf_vec_add(tar, tar, cam->position); disp = gf_vec_cross(eye, cam->up); gf_vec_norm(&disp); disp= gf_vec_scale(disp, gf_divfix(gf_mulfix(vlen, horizontal_shift), viewing_distance)); gf_vec_add(pos, cam->position, disp); gf_mx_lookat(&cam->modelview, pos, tar, cam->up); } gf_mx_add_matrix(&cam->modelview, &post_model_view); /*compute frustum planes*/ gf_mx_copy(cam->unprojection, cam->projection); gf_mx_add_matrix_4x4(&cam->unprojection, &cam->modelview); camera_frustum_from_matrix(cam, &cam->unprojection); /*also compute reverse PM for unprojections*/ gf_mx_inverse_4x4(&cam->unprojection); cam->flags &= ~CAM_IS_DIRTY; }
static void BuildTriangleFanSet(GF_Mesh *mesh, GF_Node *_coords, GF_Node *_color, GF_Node *_txcoords, GF_Node *_normal, MFInt32 *fanList, MFInt32 *indices, Bool normalPerVertex, Bool ccw, Bool solid) { u32 fan, i, cur_idx, generate_tx; GF_Vertex vx; GenMFField *cols; MFVec3f *norms; MFVec2f *txcoords; Bool rgba_col; SFColorRGBA rgba; X_Coordinate *c = (X_Coordinate *) _coords; mesh_reset(mesh); cols = NULL; rgba_col = 0; if (_color) { if (gf_node_get_tag(_color)==TAG_X3D_ColorRGBA) { rgba_col = 1; cols = (GenMFField *) & ((X_ColorRGBA *) _color)->color; } else { cols = (GenMFField *) & ((M_Color *) _color)->color; } } norms = NULL; if (_normal) norms = & ((M_Normal *)_normal)->vector; txcoords = NULL; generate_tx = 0; /*FIXME - this can't work with multitexturing*/ if (_txcoords) { switch (gf_node_get_tag(_txcoords)) { case TAG_X3D_TextureCoordinate: case TAG_MPEG4_TextureCoordinate: txcoords = & ((M_TextureCoordinate *)_txcoords)->point; break; case TAG_X3D_TextureCoordinateGenerator: generate_tx = 1; break; } } memset(&vx, 0, sizeof(GF_Vertex)); cur_idx = 0; for (fan= 0; fan<fanList->count; fan++) { u32 start_idx = mesh->v_count; if (fanList->vals[fan] < 3) continue; for (i=0; i<(u32) fanList->vals[fan]; i++) { u32 idx; if (indices) { if (indices->count<=cur_idx) return; if (indices->vals[cur_idx] == -1) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[X3D] bad formatted X3D triangle set\n")); return; } idx = indices->vals[cur_idx]; } else { idx = cur_idx; } vx.pos = c->point.vals[idx]; if (cols && (cols->count>idx)) { if (rgba_col) { rgba = ((MFColorRGBA *)cols)->vals[idx]; } else { rgba = gf_sg_sfcolor_to_rgba( ((MFColor *)cols)->vals[idx]); } vx.color = MESH_MAKE_COL(rgba); } /*according to X3D spec, if normal field is set, it is ALWAYS as normal per vertex*/ if (norms && (norms->count>idx)) { SFVec3f n = norms->vals[idx]; gf_vec_norm(&n); MESH_SET_NORMAL(vx, n); } if (txcoords) { if (txcoords->count>idx) vx.texcoords = txcoords->vals[idx]; } /*X3D says nothing about default texture mapping here...*/ else if (!generate_tx) { switch (idx%3) { case 2: vx.texcoords.x = FIX_ONE; vx.texcoords.y = 0; break; case 1: vx.texcoords.x = FIX_ONE/2; vx.texcoords.y = FIX_ONE; break; case 0: vx.texcoords.x = 0; vx.texcoords.y = 0; break; } } mesh_set_vertex_vx(mesh, &vx); cur_idx ++; if (indices) { if (cur_idx>=indices->count) break; } else if (cur_idx==c->point.count) break; if (i>1) { mesh_set_vertex_vx(mesh, &mesh->vertices[start_idx]); mesh_set_vertex_vx(mesh, &vx); } } for (i=start_idx; i<mesh->v_count; i+=3) { mesh_set_triangle(mesh, i, i+1, i+2); } /*get rid of -1*/ if (indices && (cur_idx<indices->count) && (indices->vals[cur_idx]==-1)) cur_idx++; } if (generate_tx) mesh_generate_tex_coords(mesh, _txcoords); if (cols) mesh->flags |= MESH_HAS_COLOR; if (rgba_col) mesh->flags |= MESH_HAS_ALPHA; if (!ccw) mesh->flags |= MESH_IS_CW; if (!_normal) { mesh_recompute_normals(mesh); if (normalPerVertex) { u32 cur_face = 0; for (fan=0; fan<fanList->count; fan++) { SFVec3f n_0, n_1, n_avg, n_tot; u32 nb_face, start_face; if (fanList->vals[fan] < 3) continue; if (fanList->vals[fan] == 3) { cur_face++; continue; } start_face = cur_face; /*first face normal*/ MESH_GET_NORMAL(n_0, mesh->vertices[mesh->indices[3*cur_face]]); n_tot = n_0; cur_face++; nb_face = fanList->vals[fan] - 2; for (i=1; i<nb_face; i++) { MESH_GET_NORMAL(n_1, mesh->vertices[mesh->indices[3*cur_face + 1]]); gf_vec_add(n_avg, n_0, n_1); gf_vec_norm(&n_avg); MESH_SET_NORMAL(mesh->vertices[mesh->indices[3*cur_face + 1]], n_avg); gf_vec_add(n_tot, n_tot, n_1); n_0 = n_1; cur_face++; } /*and assign center normal*/ gf_vec_norm(&n_tot); for (i=0; i<nb_face; i++) { MESH_SET_NORMAL(mesh->vertices[mesh->indices[3*(i+start_face)]], n_tot); } } } } if (solid) mesh->flags |= MESH_IS_SOLID; mesh_update_bounds(mesh); gf_mesh_build_aabbtree(mesh); }
static void BuildTriangleStripSet(GF_Mesh *mesh, GF_Node *_coords, GF_Node *_color, GF_Node *_txcoords, GF_Node *_normal, MFInt32 *stripList, MFInt32 *indices, Bool normalPerVertex, Bool ccw, Bool solid) { u32 strip, i, cur_idx, generate_tx; GF_Vertex vx; GenMFField *cols; MFVec3f *norms; MFVec2f *txcoords; Bool rgba_col; SFColorRGBA rgba; X_Coordinate *c = (X_Coordinate *) _coords; mesh_reset(mesh); cols = NULL; rgba_col = 0; if (_color) { if (gf_node_get_tag(_color)==TAG_X3D_ColorRGBA) { rgba_col = 1; cols = (GenMFField *) & ((X_ColorRGBA *) _color)->color; } else { cols = (GenMFField *) & ((M_Color *) _color)->color; } } norms = NULL; if (_normal) norms = & ((M_Normal *)_normal)->vector; txcoords = NULL; generate_tx = 0; /*FIXME - this can't work with multitexturing*/ if (_txcoords) { switch (gf_node_get_tag(_txcoords)) { case TAG_X3D_TextureCoordinate: case TAG_MPEG4_TextureCoordinate: txcoords = & ((M_TextureCoordinate *)_txcoords)->point; break; case TAG_X3D_TextureCoordinateGenerator: generate_tx = 1; break; } } memset(&vx, 0, sizeof(GF_Vertex)); cur_idx = 0; for (strip= 0; strip<stripList->count; strip++) { u32 start_idx = mesh->v_count; if (stripList->vals[strip] < 3) continue; for (i=0; i<(u32) stripList->vals[strip]; i++) { u32 idx; if (indices) { if (indices->count<=cur_idx) return; if (indices->vals[cur_idx] == -1) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[X3D] bad formatted X3D triangle strip\n")); return; } idx = indices->vals[cur_idx]; } else { idx = cur_idx; } vx.pos = c->point.vals[idx]; if (cols && (cols->count>idx)) { if (rgba_col) { rgba = ((MFColorRGBA *)cols)->vals[idx]; } else { rgba = gf_sg_sfcolor_to_rgba( ((MFColor *)cols)->vals[idx]); } vx.color = MESH_MAKE_COL(rgba); } /*according to X3D spec, if normal field is set, it is ALWAYS as normal per vertex*/ if (norms && (norms->count>idx)) { SFVec3f n = norms->vals[idx]; gf_vec_norm(&n); MESH_SET_NORMAL(vx, n); } if (txcoords) { if (txcoords->count>idx) vx.texcoords = txcoords->vals[idx]; } /*X3D says nothing about default texture mapping here...*/ else if (!generate_tx) { switch (idx%3) { case 2: vx.texcoords.x = FIX_ONE; vx.texcoords.y = 0; break; case 1: vx.texcoords.x = FIX_ONE/2; vx.texcoords.y = FIX_ONE; break; case 0: vx.texcoords.x = 0; vx.texcoords.y = 0; break; } } if (i>2) { /*duplicate last 2 vertices (we really need independent vertices to handle normals per face)*/ mesh_set_vertex_vx(mesh, &mesh->vertices[mesh->v_count-2]); mesh_set_vertex_vx(mesh, &mesh->vertices[mesh->v_count-2]); } mesh_set_vertex_vx(mesh, &vx); cur_idx ++; if (indices) { if (cur_idx>=indices->count) break; } else if (cur_idx==c->point.count) break; } for (i=start_idx; i<mesh->v_count; i+=3) { mesh_set_triangle(mesh, i, i+1, i+2); } /*get rid of -1*/ if (indices && (cur_idx<indices->count) && (indices->vals[cur_idx]==-1)) cur_idx++; } if (generate_tx) mesh_generate_tex_coords(mesh, _txcoords); if (cols) mesh->flags |= MESH_HAS_COLOR; if (rgba_col) mesh->flags |= MESH_HAS_ALPHA; if (_normal) { if (!ccw) mesh->flags |= MESH_IS_CW; } /*reorder everything to CCW*/ else { u32 cur_face = 0; mesh_recompute_normals(mesh); for (i=0; i<mesh->i_count; i+= 3) { if ((ccw && (cur_face%2)) || (!ccw && !(cur_face%2))) { SFVec3f v; u32 idx; MESH_GET_NORMAL(v, mesh->vertices[mesh->indices[i]]); v = gf_vec_scale(v,-1); MESH_SET_NORMAL(mesh->vertices[mesh->indices[i]], v); mesh->vertices[mesh->indices[i+1]].normal = mesh->vertices[mesh->indices[i]].normal; mesh->vertices[mesh->indices[i+2]].normal = mesh->vertices[mesh->indices[i]].normal; idx = mesh->indices[i+2]; mesh->indices[i+2] = mesh->indices[i+1]; mesh->indices[i+1] = idx; } cur_face++; } if (normalPerVertex) { cur_face = 0; for (strip=0; strip<stripList->count; strip++) { SFVec3f n_0, n_1, n_2, n_avg; u32 nb_face; if (stripList->vals[strip] < 3) continue; if (stripList->vals[strip] <= 3) { cur_face ++; continue; } /*first face normal*/ MESH_GET_NORMAL(n_0, mesh->vertices[mesh->indices[3*cur_face]]); /*second face normal*/ MESH_GET_NORMAL(n_1, mesh->vertices[mesh->indices[3*(cur_face+1)]]); gf_vec_add(n_avg, n_0, n_1); gf_vec_norm(&n_avg); /*assign to second point of first face and first of second face*/ MESH_SET_NORMAL(mesh->vertices[mesh->indices[3*cur_face+1]], n_avg); MESH_SET_NORMAL(mesh->vertices[mesh->indices[3*(cur_face+1)]], n_avg); nb_face = stripList->vals[strip] - 2; cur_face++; for (i=1; i<nb_face-1; i++) { /*get normal (use second pt of current face since first has been updated)*/ MESH_GET_NORMAL(n_2, mesh->vertices[mesh->indices[3*cur_face + 1]]); gf_vec_add(n_avg, n_0, n_1); gf_vec_add(n_avg, n_avg, n_2); gf_vec_norm(&n_avg); /*last of prev face*/ MESH_SET_NORMAL(mesh->vertices[mesh->indices[3*cur_face - 1]], n_avg); /*second of current face*/ mesh->vertices[mesh->indices[3*cur_face + 1]].normal = mesh->vertices[mesh->indices[3*cur_face - 1]].normal; /*first of next face*/ mesh->vertices[mesh->indices[3*cur_face + 3]].normal = mesh->vertices[mesh->indices[3*cur_face - 1]].normal; n_0 = n_1; n_1 = n_2; cur_face++; } } } } if (solid) mesh->flags |= MESH_IS_SOLID; mesh_update_bounds(mesh); gf_mesh_build_aabbtree(mesh); }
static Bool OnCylinderSensor(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, GF_Event *ev, GF_Compositor *compositor) { Bool is_mouse = (ev->type<=GF_EVENT_MOUSEWHEEL) ? 1 : 0; M_CylinderSensor *cs = (M_CylinderSensor *)sh->sensor; CylinderSensorStack *st = (CylinderSensorStack *) gf_node_get_private(sh->sensor); if (cs->isActive && (!cs->enabled || /*mouse*/((ev->type==GF_EVENT_MOUSEUP) && (ev->mouse.button==GF_MOUSE_LEFT)) || /*keyboar*/(!is_mouse && (!is_over|| ((ev->type==GF_EVENT_KEYDOWN) && (ev->key.key_code==GF_KEY_ENTER)))) ) ) { if (cs->autoOffset) { cs->offset = cs->rotation_changed.q; if (!is_cancel) gf_node_event_out(sh->sensor, 5/*"offset"*/); } cs->isActive = 0; if (!is_cancel) gf_node_event_out(sh->sensor, 6/*"isActive"*/); sh->grabbed = 0; return is_cancel ? 0 : 1; } else if (is_mouse) { if (!cs->isActive && (ev->type==GF_EVENT_MOUSEDOWN) && (ev->mouse.button==GF_MOUSE_LEFT)) { GF_Ray r; SFVec3f yaxis; Fixed acute, reva; SFVec3f bearing; gf_mx_copy(st->init_matrix, compositor->hit_world_to_local); /*get initial angle & check disk mode*/ r = compositor->hit_world_ray; gf_vec_add(r.dir, r.orig, r.dir); gf_mx_apply_vec(&compositor->hit_world_to_local, &r.orig); gf_mx_apply_vec(&compositor->hit_world_to_local, &r.dir); gf_vec_diff(bearing, r.orig, r.dir); gf_vec_norm(&bearing); yaxis.x = yaxis.z = 0; yaxis.y = FIX_ONE; acute = gf_vec_dot(bearing, yaxis); if (acute < -FIX_ONE) acute = -FIX_ONE; else if (acute > FIX_ONE) acute = FIX_ONE; acute = gf_acos(acute); reva = ABS(GF_PI - acute); if (reva<acute) acute = reva; st->disk_mode = (acute < cs->diskAngle) ? 1 : 0; st->grab_start = compositor->hit_local_point; /*cos we're lazy*/ st->yplane.d = 0; st->yplane.normal.x = st->yplane.normal.z = st->yplane.normal.y = 0; st->zplane = st->xplane = st->yplane; st->xplane.normal.x = FIX_ONE; st->yplane.normal.y = FIX_ONE; st->zplane.normal.z = FIX_ONE; cs->rotation_changed.x = 0; cs->rotation_changed.y = FIX_ONE; cs->rotation_changed.z = 0; cs->isActive = 1; gf_node_event_out(sh->sensor, 6/*"isActive"*/); sh->grabbed = 1; return 1; } else if (cs->isActive) { GF_Ray r; Fixed radius, rot; SFVec3f dir1, dir2, cx; if (is_over) { cs->trackPoint_changed = compositor->hit_local_point; gf_node_event_out(sh->sensor, 8/*"trackPoint_changed"*/); } else { GF_Plane project_to; r = compositor->hit_world_ray; gf_mx_apply_ray(&st->init_matrix, &r); /*no intersection, use intersection with "main" fronting plane*/ if ( ABS(r.dir.z) > ABS(r.dir.y)) { if (ABS(r.dir.z) > ABS(r.dir.x)) project_to = st->xplane; else project_to = st->yplane; } else { if (ABS(r.dir.z) > ABS(r.dir.x)) project_to = st->xplane; else project_to = st->zplane; } if (!gf_plane_intersect_line(&project_to, &r.orig, &r.dir, &compositor->hit_local_point)) return 0; } dir1.x = compositor->hit_local_point.x; dir1.y = 0; dir1.z = compositor->hit_local_point.z; if (st->disk_mode) { radius = FIX_ONE; } else { radius = gf_vec_len(dir1); } gf_vec_norm(&dir1); dir2.x = st->grab_start.x; dir2.y = 0; dir2.z = st->grab_start.z; gf_vec_norm(&dir2); cx = gf_vec_cross(dir2, dir1); gf_vec_norm(&cx); if (gf_vec_len(cx)<FIX_EPSILON) return 0; rot = gf_mulfix(radius, gf_acos(gf_vec_dot(dir2, dir1)) ); if (fabs(cx.y + FIX_ONE) < FIX_EPSILON) rot = -rot; if (cs->autoOffset) rot += cs->offset; if (cs->minAngle < cs->maxAngle) { if (rot < cs->minAngle) rot = cs->minAngle; else if (rot > cs->maxAngle) rot = cs->maxAngle; } cs->rotation_changed.q = rot; gf_node_event_out(sh->sensor, 7/*"rotation_changed"*/); return 1; } } else { if (!cs->isActive && is_over && (ev->type==GF_EVENT_KEYDOWN) && (ev->key.key_code==GF_KEY_ENTER)) { cs->isActive = 1; cs->rotation_changed.q = cs->offset; cs->rotation_changed.x = cs->rotation_changed.z = 0; cs->rotation_changed.y = FIX_ONE; gf_node_event_out(sh->sensor, 6/*"isActive"*/); return 1; } else if (cs->isActive && (ev->type==GF_EVENT_KEYDOWN)) { SFFloat res; Fixed diff = (ev->key.flags & GF_KEY_MOD_SHIFT) ? GF_PI/8 : GF_PI/64; res = cs->rotation_changed.q; switch (ev->key.key_code) { case GF_KEY_LEFT: res -= diff; break; case GF_KEY_RIGHT: res += diff; break; case GF_KEY_HOME: res = cs->offset; break; default: return 0; } /*clip*/ if (cs->minAngle <= cs->maxAngle) { if (res < cs->minAngle) res = cs->minAngle; if (res > cs->maxAngle) res = cs->maxAngle; } cs->rotation_changed.q = res; gf_node_event_out(sh->sensor, 7/*"rotation_changed"*/); return 1; } } return 0; }
void TraverseProximitySensor(GF_Node *node, void *rs, Bool is_destroy) { SFVec3f user_pos, dist, up; SFRotation ori; GF_Matrix mx; GF_TraverseState *tr_state = (GF_TraverseState *)rs; M_ProximitySensor *ps = (M_ProximitySensor *)node; if (is_destroy) return; if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) { /*work with twice bigger bbox to get sure we're notify when culled out*/ gf_vec_add(tr_state->bbox.max_edge, ps->center, ps->size); gf_vec_diff(tr_state->bbox.min_edge, ps->center, ps->size); gf_bbox_refresh(&tr_state->bbox); return; } else if (!ps->enabled || (tr_state->traversing_mode != TRAVERSE_SORT) ) return; /*TODO FIXME - find a way to cache inverted matrix*/ gf_mx_copy(mx, tr_state->model_matrix); gf_mx_inverse(&mx); /*get use pos in local coord system*/ user_pos = tr_state->camera->position; gf_mx_apply_vec(&mx, &user_pos); gf_vec_diff(dist, user_pos, ps->center); if (dist.x<0) dist.x *= -1; if (dist.y<0) dist.y *= -1; if (dist.z<0) dist.z *= -1; if ((2*dist.x <= ps->size.x) && (2*dist.y <= ps->size.y) && (2*dist.z <= ps->size.z) ) { if (!ps->isActive) { ps->isActive = 1; gf_node_event_out(node, 3/*"isActive"*/); ps->enterTime = gf_node_get_scene_time(node); gf_node_event_out(node, 6/*"enterTime"*/); } if ((ps->position_changed.x != user_pos.x) || (ps->position_changed.y != user_pos.y) || (ps->position_changed.z != user_pos.z) ) { ps->position_changed = user_pos; gf_node_event_out(node, 4/*"position_changed"*/); } dist = tr_state->camera->target; gf_mx_apply_vec(&mx, &dist); up = tr_state->camera->up; gf_mx_apply_vec(&mx, &up); ori = camera_get_orientation(user_pos, dist, tr_state->camera->up); if ((ori.q != ps->orientation_changed.q) || (ori.x != ps->orientation_changed.x) || (ori.y != ps->orientation_changed.y) || (ori.z != ps->orientation_changed.z) ) { ps->orientation_changed = ori; gf_node_event_out(node, 5/*"orientation_changed"*/); } } else if (ps->isActive) { ps->isActive = 0; gf_node_event_out(node, 3/*"isActive"*/); ps->exitTime = gf_node_get_scene_time(node); gf_node_event_out(node, 7/*"exitTime"*/); } }
Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor) { GF_TraverseState tr_state; SFVec3f pos, diff; Fixed dist, d; GF_Camera *cam; GF_Node *top; #ifndef GPAC_DISABLE_VRML // if (gf_list_count(compositor->visual->back_stack)) return; if (gf_list_count(compositor->visual->view_stack)) return 0; #endif gf_mx_p(compositor->mx); top = gf_sg_get_root_node(compositor->scene); if (!top) { gf_mx_v(compositor->mx); return 0; } memset(&tr_state, 0, sizeof(GF_TraverseState)); gf_mx_init(tr_state.model_matrix); tr_state.traversing_mode = TRAVERSE_GET_BOUNDS; tr_state.visual = compositor->visual; gf_node_traverse(top, &tr_state); if (gf_node_dirty_get(top)) { tr_state.bbox.is_set = 0; } if (!tr_state.bbox.is_set) { gf_mx_v(compositor->mx); /*empty world ...*/ if (tr_state.bbox.radius==-1) return 1; /*2D world with 3D camera forced*/ if (tr_state.bounds.width&&tr_state.bounds.height) return 1; return 0; } cam = &compositor->visual->camera; cam->world_bbox = tr_state.bbox; /*fit is based on bounding sphere*/ dist = gf_divfix(tr_state.bbox.radius, gf_sin(cam->fieldOfView/2) ); gf_vec_diff(diff, cam->center, tr_state.bbox.center); /*do not update if camera is outside the scene bounding sphere and dist is too close*/ if (gf_vec_len(diff) > tr_state.bbox.radius + cam->radius) { gf_vec_diff(diff, cam->vp_position, tr_state.bbox.center); d = gf_vec_len(diff); if (d<dist) { gf_mx_v(compositor->mx); return 1; } } diff = gf_vec_scale(camera_get_pos_dir(cam), dist); gf_vec_add(pos, tr_state.bbox.center, diff); diff = cam->position; camera_set_vectors(cam, pos, cam->vp_orientation, cam->fieldOfView); cam->position = diff; camera_move_to(cam, pos, cam->target, cam->up); cam->examine_center = tr_state.bbox.center; cam->flags |= CF_STORE_VP; if (cam->z_far < dist) cam->z_far = 10*dist; camera_changed(compositor, cam); gf_mx_v(compositor->mx); return 1; }
Bool camera_animate(GF_Camera *cam) { u32 now; Fixed frac; if (!cam->anim_len) return GF_FALSE; if (cam->jumping) { if (!cam->anim_start) { cam->anim_start = gf_sys_clock(); cam->dheight = 0; return GF_TRUE; } cam->position.y -= cam->dheight; cam->target.y -= cam->dheight; now = gf_sys_clock() - cam->anim_start; if (now > cam->anim_len) { cam->anim_len = 0; cam->jumping = GF_FALSE; cam->flags |= CAM_IS_DIRTY; return GF_TRUE; } frac = FLT2FIX ( ((Float) now) / cam->anim_len); if (frac>FIX_ONE / 2) frac = FIX_ONE - frac; cam->dheight = gf_mulfix(cam->avatar_size.y, frac); cam->position.y += cam->dheight; cam->target.y += cam->dheight; cam->flags |= CAM_IS_DIRTY; return GF_TRUE; } if (!cam->anim_start) { cam->anim_start = gf_sys_clock(); now = 0; frac = 0; } else { now = gf_sys_clock() - cam->anim_start; if (now > cam->anim_len) { cam->anim_len = 0; #ifndef FORCE_CAMERA_3D if (cam->is_3D) #endif { camera_set_vectors(cam, cam->end_pos, cam->end_ori, cam->end_fov); cam->end_zoom = FIX_ONE; } #ifndef FORCE_CAMERA_3D else { cam->flags |= CAM_IS_DIRTY; } #endif if (cam->flags & CF_STORE_VP) { cam->flags &= ~CF_STORE_VP; cam->vp_position = cam->position; cam->vp_fov = cam->fieldOfView; cam->vp_orientation = camera_get_orientation(cam->position, cam->target, cam->up); } return GF_TRUE; } else { frac = FLT2FIX( ((Float) now) / cam->anim_len); } } #ifndef FORCE_CAMERA_3D if (cam->is_3D) #endif { SFVec3f pos, dif; SFRotation rot; Fixed fov; rot = gf_sg_sfrotation_interpolate(cam->start_ori, cam->end_ori, frac); gf_vec_diff(dif, cam->end_pos, cam->start_pos); dif = gf_vec_scale(dif, frac); gf_vec_add(pos, cam->start_pos, dif); fov = gf_mulfix(cam->end_fov - cam->start_fov, frac) + cam->start_fov; cam->end_zoom = frac + gf_mulfix((FIX_ONE-frac), cam->start_zoom); camera_set_vectors(cam, pos, rot, fov); } return GF_TRUE; }
static void TraverseBillboard(GF_Node *n, void *rs, Bool is_destroy) { GF_Matrix gf_mx_bckup; TransformStack *st = (TransformStack *)gf_node_get_private(n); M_Billboard *bb = (M_Billboard *)n; GF_TraverseState *tr_state = (GF_TraverseState *)rs; if (is_destroy) { DestroyTransform(n); return; } if (! tr_state->camera) return; /*can't cache the matrix here*/ gf_mx_init(st->mx); if (tr_state->camera->is_3D) { SFVec3f z, axis; Fixed axis_len; SFVec3f user_pos = tr_state->camera->position; gf_mx_apply_vec(&tr_state->model_matrix, &user_pos); gf_vec_norm(&user_pos); axis = bb->axisOfRotation; axis_len = gf_vec_len(axis); if (axis_len<FIX_EPSILON) { SFVec3f x, y, t; /*get user's right in local coord*/ gf_vec_diff(t, tr_state->camera->position, tr_state->camera->target); gf_vec_norm(&t); x = gf_vec_cross(tr_state->camera->up, t); gf_vec_norm(&x); gf_mx_rotate_vector(&tr_state->model_matrix, &x); gf_vec_norm(&x); /*get user's up in local coord*/ y = tr_state->camera->up; gf_mx_rotate_vector(&tr_state->model_matrix, &y); gf_vec_norm(&y); z = gf_vec_cross(x, y); gf_vec_norm(&z); gf_mx_rotation_matrix_from_vectors(&st->mx, x, y, z); gf_mx_inverse(&st->mx); } else { SFVec3f tmp; Fixed d, cosw, sinw, angle; gf_vec_norm(&axis); /*map eye & z into plane with normal axis through 0.0*/ d = -gf_vec_dot(axis, user_pos); tmp = gf_vec_scale(axis, d); gf_vec_add(user_pos, user_pos, tmp); gf_vec_norm(&user_pos); z.x = z.y = 0; z.z = FIX_ONE; d = -gf_vec_dot(axis, z); tmp = gf_vec_scale(axis, d); gf_vec_add(z, z, tmp); gf_vec_norm(&z); cosw = gf_vec_dot(user_pos, z); tmp = gf_vec_cross(user_pos, z); sinw = gf_vec_len(tmp); angle = gf_acos(cosw); gf_vec_norm(&tmp); if ((sinw>0) && (gf_vec_dot(axis, tmp) > 0)) gf_vec_rev(axis); gf_mx_add_rotation(&st->mx, angle, axis.x, axis.y, axis.z); } } gf_mx_copy(gf_mx_bckup, tr_state->model_matrix); gf_mx_add_matrix(&tr_state->model_matrix, &st->mx); /*note we don't clear dirty flag, this is done in traversing*/ group_3d_traverse(n, (GroupingNode *) st, tr_state); gf_mx_copy(tr_state->model_matrix, gf_mx_bckup); if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) gf_mx_apply_bbox(&st->mx, &tr_state->bbox); }
static void OnCylinderSensor(SensorHandler *sh, Bool is_over, GF_Event *ev, RayHitInfo *hit_info) { M_CylinderSensor *cs = (M_CylinderSensor *)sh->owner; CylinderSensorStack *st = (CylinderSensorStack *) gf_node_get_private(sh->owner); if (cs->isActive && (!cs->enabled || ((ev->type==GF_EVENT_MOUSEUP) && (ev->mouse.button==GF_MOUSE_LEFT))) ) { if (cs->autoOffset) { cs->offset = cs->rotation_changed.q; gf_node_event_out_str(sh->owner, "offset"); } cs->isActive = 0; gf_node_event_out_str(sh->owner, "isActive"); R3D_SetGrabbed(st->compositor, 0); } else if (!cs->isActive && (ev->type==GF_EVENT_MOUSEDOWN) && (ev->mouse.button==GF_MOUSE_LEFT)) { GF_Ray r; SFVec3f yaxis; Fixed acute, reva; SFVec3f bearing; gf_mx_copy(st->init_matrix, hit_info->world_to_local); /*get initial angle & check disk mode*/ r = hit_info->world_ray; gf_vec_add(r.dir, r.orig, r.dir); gf_mx_apply_vec(&hit_info->world_to_local, &r.orig); gf_mx_apply_vec(&hit_info->world_to_local, &r.dir); gf_vec_diff(bearing, r.orig, r.dir); gf_vec_norm(&bearing); yaxis.x = yaxis.z = 0; yaxis.y = FIX_ONE; acute = gf_vec_dot(bearing, yaxis); if (acute < -FIX_ONE) acute = -FIX_ONE; else if (acute > FIX_ONE) acute = FIX_ONE; acute = gf_acos(acute); reva = ABS(GF_PI - acute); if (reva<acute) acute = reva; st->disk_mode = (acute < cs->diskAngle) ? 1 : 0; st->grab_start = hit_info->local_point; /*cos we're lazy*/ st->yplane.d = 0; st->yplane.normal.x = st->yplane.normal.z = st->yplane.normal.y = 0; st->zplane = st->xplane = st->yplane; st->xplane.normal.x = FIX_ONE; st->yplane.normal.y = FIX_ONE; st->zplane.normal.z = FIX_ONE; cs->rotation_changed.x = 0; cs->rotation_changed.y = FIX_ONE; cs->rotation_changed.z = 0; cs->isActive = 1; gf_node_event_out_str(sh->owner, "isActive"); R3D_SetGrabbed(st->compositor, 1); } else if (cs->isActive) { GF_Ray r; Fixed radius, rot; SFVec3f dir1, dir2, cx; if (is_over) { cs->trackPoint_changed = hit_info->local_point; gf_node_event_out_str(sh->owner, "trackPoint_changed"); } else { GF_Plane project_to; r = hit_info->world_ray; gf_mx_apply_ray(&st->init_matrix, &r); /*no intersection, use intersection with "main" fronting plane*/ if ( ABS(r.dir.z) > ABS(r.dir.y)) { if (ABS(r.dir.x) > ABS(r.dir.x)) project_to = st->xplane; else project_to = st->zplane; } else project_to = st->yplane; if (!gf_plane_intersect_line(&project_to, &r.orig, &r.dir, &hit_info->local_point)) return; } dir1.x = hit_info->local_point.x; dir1.y = 0; dir1.z = hit_info->local_point.z; if (st->disk_mode) { radius = FIX_ONE; } else { radius = gf_vec_len(dir1); } gf_vec_norm(&dir1); dir2.x = st->grab_start.x; dir2.y = 0; dir2.z = st->grab_start.z; gf_vec_norm(&dir2); cx = gf_vec_cross(dir2, dir1); gf_vec_norm(&cx); if (gf_vec_len(cx)<FIX_EPSILON) return; rot = gf_mulfix(radius, gf_acos(gf_vec_dot(dir2, dir1)) ); if (fabs(cx.y + FIX_ONE) < FIX_EPSILON) rot = -rot; if (cs->autoOffset) rot += cs->offset; if (cs->minAngle < cs->maxAngle) { if (rot < cs->minAngle) rot = cs->minAngle; else if (rot > cs->maxAngle) rot = cs->maxAngle; } cs->rotation_changed.q = rot; gf_node_event_out_str(sh->owner, "rotation_changed"); } }