static int mouse_press (Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3], const GdkEventButton *event) { RendererGoal *self = (RendererGoal*) ehandler->user; double xy[2]; geom_ray_z_plane_intersect_3d(POINT3D (ray_start), POINT3D (ray_dir), 0, POINT2D (xy)); int consumed = 0; int control = event->state & GDK_CONTROL_MASK; pthread_mutex_lock (&self->mutex); // select a previously selected goal? if (event->button == 1 && ehandler->picking) { self->edit_goal = find_goal(self, xy); consumed = 1; } if (event->button == 1 && control && ehandler->picking && self->edit_goal == NULL) { // create a new goal lcmtypes_goal_t g = { .pos = { xy[0], xy[1] }, .size = { 1, 1 }, .theta = 0,
static double pick_query(Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3]) { RendererCar *self = (RendererCar*) ehandler->user; lcmtypes_pose_t pose; if (ctrans_local_pose (self->ctrans, &pose) < 0) return -1; double ray_start_body[3]; vector_subtract_3d (ray_start, pose.pos, ray_start_body); rot_quat_rotate_rev (pose.orientation, ray_start_body); double ray_dir_body[3] = { ray_dir[0], ray_dir[1], ray_dir[2] }; rot_quat_rotate_rev (pose.orientation, ray_dir_body); vector_normalize_3d (ray_dir_body); point3d_t car_pos_body = { 1.3, 0, 1 }; point3d_t box_size = { 4.6, 2, 1.4 }; double t = geom_ray_axis_aligned_box_intersect_3d (POINT3D(ray_start_body), POINT3D (ray_dir_body), &car_pos_body, &box_size, NULL); if (isfinite (t)) return t; self->ehandler.hovering = 0; return -1; }
point3d_t Object::calculate(const point3d_t point) const { boost::numeric::ublas::vector<double> v(4); v(0) = point.x; v(1) = point.y; v(2) = point.z; v(3) = 1.0; v = prod(t, v); if(parent) return parent->calculate(POINT3D(v(0),v(1),v(2))); return POINT3D(v(0),v(1),v(2)); }
static int mouse_release(Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3], const GdkEventButton *event) { RendererSimTraffic *self = (RendererSimTraffic*) ehandler->user; if (self->ehandler.picking) { if (!ctrans_have_pose (self->ctrans) || !ctrans_have_gps_to_local (self->ctrans)) { self->ehandler.picking = 0; return 0; } double car_pos[3]; ctrans_local_pos (self->ctrans, car_pos); geom_ray_z_plane_intersect_3d(POINT3D(ray_start), POINT3D(ray_dir), car_pos[2], POINT2D(self->last_xy)); lcmtypes_track_t msg = { .id = self->selected_vehicle_id, .pos = { self->last_xy[0], self->last_xy[1] }, .vel = { 0, 0 }, .size = { 0, 0 }, .size_good = TRUE, .theta = 0, .confidence = 3, }; lcmtypes_track_t_publish (self->lc, "TSIM_MOVE", &msg); } self->ehandler.picking = 0; return 0; } static int mouse_motion (Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3], const GdkEventMotion *event) { RendererSimTraffic *self = (RendererSimTraffic*) ehandler->user; if (!self->ehandler.picking || !event->state & GDK_BUTTON1_MASK) return 0; if (!ctrans_have_pose (self->ctrans) || !ctrans_have_gps_to_local (self->ctrans)) { return 0; } double car_pos[3]; ctrans_local_pos (self->ctrans, car_pos); geom_ray_z_plane_intersect_3d (POINT3D(ray_start), POINT3D(ray_dir), car_pos[2], POINT2D(self->last_xy)); return 1; }
static double pick_query(Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3]) { RendererSimTraffic *self = (RendererSimTraffic*) ehandler->user; if (!ctrans_have_pose (self->ctrans)) return -1; if (!self->tracks) return -1; double car_pos[3]; ctrans_local_pos (self->ctrans, car_pos); vec3d_t rd = { ray_dir[0], ray_dir[1], ray_dir[2] }; geom_vec_normalize_3d (&rd); double tmin = INFINITY; for (int i=0; i<self->tracks->ntracks; i++) { lcmtypes_track_t *vd = &self->tracks->tracks[i]; point3d_t box_size = { vd->size[0], vd->size[1], VEHICLE_HEIGHT }; // transform the ray into object frame double rs_obj[3] = { ray_start[0] - vd->pos[0], ray_start[1] - vd->pos[1], ray_start[2] - (car_pos[2] + VEHICLE_HEIGHT / 2) }; double rquat[4]; double zaxis[3] = { 0, 0, 1 }; rot_angle_axis_to_quat (vd->theta, zaxis, rquat); rot_quat_rotate_rev (rquat, rs_obj); double rd_obj[3] = { rd.x, rd.y, rd.z }; rot_quat_rotate_rev (rquat, rd_obj); point3d_t pos = { 0, 0, 0 }; // in object frame, intersection test is easy double t = geom_ray_axis_aligned_box_intersect_3d (POINT3D(rs_obj), POINT3D(rd_obj), &pos, &box_size, NULL); if (isfinite (t) && isless (t, tmin)) { tmin = t; self->selected_vehicle_id = vd->id; } } if (isfinite (tmin)) { return tmin; } self->ehandler.hovering = 0; return -1; }
int main(int argc, char** argv) { if(argc!=2) { std::cerr << "Usage: " << argv[0] << " <file-name>" << std::endl; return -1; } World world; //(COLOR(0.3, 0.3, 1.0)); Light light1(&world, POINT3D(2.0, 3.0, 2.0)); //Light light2(&world, POINT3D(-4.0, 5.0, 5.0), LIGHT(COLOR_YELLOW)); //Light light3(&world, POINT3D(2.0, 0.0, 5.0)); material_t blue_ball_mat = MATERIAL_DEFAULT, green_ball_mat = MATERIAL_DEFAULT; blue_ball_mat.color = COLOR_WHITE; green_ball_mat.color = COLOR(0.7,0.7,0.7); blue_ball_mat.specular = green_ball_mat.specular = COLOR_WHITE; blue_ball_mat.ka = 0.075; green_ball_mat.ka = 0.15; blue_ball_mat.kd = 0.075; green_ball_mat.kd = 0.25; blue_ball_mat.ks = 0.2; green_ball_mat.ks = 1.0; blue_ball_mat.ke = green_ball_mat.ke = 20.0; blue_ball_mat.kr = 0.05; green_ball_mat.kr = 0.75; blue_ball_mat.kt = 0.85; blue_ball_mat.n = 0.95; blue_ball_mat.texture = green_ball_mat.texture = 0; Sphere blue(&world, blue_ball_mat, 0.15, POINT3D(0.45, 0.3, 0.75)); Sphere green(&world, green_ball_mat, 0.15, POINT3D(0.3, 0.2, 0.5)); Object floor(&world); material_t floor_mat = MATERIAL_DEFAULT; floor_mat.color = COLOR_RED; floor_mat.specular = COLOR_WHITE; floor_mat.ka = 0.7; floor_mat.kd = 0.2; floor_mat.ks = 0.2; floor_mat.ke = 10.0; floor_mat.texture = floor_texture1; Triangle floor1(&floor, floor_mat, POINT3D(-0.7, 0.0, 1.0), POINT3D(1.05, 0.0, 1.0), POINT3D(1.05, 0.0, -1.5)); Triangle floor2(&floor, floor_mat, POINT3D(-0.7, 0.0, 1.0), POINT3D(1.05, 0.0, -1.5), POINT3D(-0.7, 0.0, -1.5)); //Cube cube(&world, MATERIAL(COLOR_YELLOW), POINT3D(0.4, 0.4, 0.4), POINT3D(1.0, 0.5, -0.5), POINT3D(M_PI/4.0, -M_PI / 4.0, M_PI/4.0)); //Cone cone(&world, MATERIAL(COLOR_YELLOW), 1.0, 5, 0.5, 20, POINT3D(0.0, 0.5, -0.5), POINT3D(-M_PI/16.0,0.0,0.0)); Camera cam(&world, POINT3D(0.5, 0.3, 1.5), POINT3D(0.5, 0.3, 0.0), POINT3D(0.0, 1.0, 0.0), 45.0, 0.01, 10.0, 10); Image img(1024,768); cam.snap(&img); Image::save(&img, argv[1]); return 0; }
static int mouse_motion (Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3], const GdkEventMotion *event) { RendererCar *self = (RendererCar*) ehandler->user; if (!viewer->simulation_flag||!self->teleport_car) return 0; if (!self->ehandler.picking) return 0; lcmtypes_pose_t p; if (ctrans_local_pose (self->ctrans, &p) < 0) return 0; double xy[2]; geom_ray_z_plane_intersect_3d(POINT3D(ray_start), POINT3D(ray_dir), p.pos[2], POINT2D(xy)); int shift = event->state & GDK_SHIFT_MASK; if (shift) { // rotate! double theta1 = atan2(xy[1] - p.pos[1], xy[0] - p.pos[0]); // local double dq[4] = { cos (theta1/2), 0, 0, sin(theta1/2) }; memcpy(p.orientation, dq, 4 * sizeof(double)); } else { // translate p.pos[0] = xy[0]; p.pos[1] = xy[1]; } self->last_xy[0] = xy[0]; self->last_xy[1] = xy[1]; lcmtypes_pose_t_publish(self->lc, "SIM_TELEPORT", &p); return 1; }
static int mouse_press (Viewer *viewer, EventHandler *ehandler, const double ray_start[3], const double ray_dir[3], const GdkEventButton *event) { RendererCar *self = (RendererCar*) ehandler->user; // only handle mouse button 1. if (self->teleport_car_request&&(event->button == 1)) { self->teleport_car = 1; } if (event->type == GDK_2BUTTON_PRESS) { self->display_detail = (self->display_detail + 1) % NUM_DETAILS; viewer_request_redraw(self->viewer); } double carpos[3] = { 0, 0, 0 }; ctrans_local_pos (self->ctrans, carpos); geom_ray_z_plane_intersect_3d(POINT3D(ray_start), POINT3D(ray_dir), carpos[2], POINT2D(self->last_xy)); return 0; }
static void point3d_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { Point3D *point3d = POINT3D(obj); g_print("point3d_get_property(obj=%p, %s)\n", obj, g_param_spec_get_name(pspec)); switch (prop_id) { case PROP_Z: g_value_set_int(value, point3d->z); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } }
static void point3d_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { Point3D *point3d = POINT3D(obj); { gchar *contents = g_strdup_value_contents(value); g_print("point3d_set_property(obj=%p, %s => %s)\n", obj, g_param_spec_get_name(pspec), contents); g_free(contents); } switch (prop_id) { case PROP_Z: { gint new_z = g_value_get_int(value); point3d->z = new_z; break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } }
void Init() { // ::SetTimer(g_hwnd,100,1000,NULL); bool bret = LoadBitmapFile(&bitmap,"wall.bmp"); int bitmapwidth = bitmap.bitmapinfoheader.biWidth; int bitmapheight = bitmap.bitmapinfoheader.biHeight; CRenderObject *pobj = new CRenderObject; std::vector<Vertex> pointlist; pointlist.push_back(Vertex(POINT3D(10,10,10) )); pointlist.push_back(Vertex(POINT3D(10,10,-10))); pointlist.push_back(Vertex(POINT3D(-10,10,-10))); pointlist.push_back(Vertex(POINT3D(-10,10,10))); pointlist.push_back(Vertex(POINT3D(10,-10,10))); pointlist.push_back(Vertex(POINT3D(-10,-10,10))); pointlist.push_back(Vertex(POINT3D(-10,-10,-10))); pointlist.push_back(Vertex(POINT3D(10,-10,-10))); pobj->m_translateverticesList = pointlist; pobj->m_verticesList = pointlist; //Set texture pointlist std::vector<POINT2D> texturelist; texturelist.push_back(POINT2D(0,0)); texturelist.push_back(POINT2D(bitmapwidth-1,0)); texturelist.push_back(POINT2D(bitmapwidth-1,bitmapheight-1)); texturelist.push_back(POINT2D(0,bitmapheight-1)); pobj->m_texturecoordinatesList = texturelist; int tempindices[36]= { 0,1,2, 0,2,3, 0,7,1, 0,4,7, 1,7,6, 1,6,2, 2,6,5, 2,3,5, 0,5,4, 0,3,5, 5,6,7, 4,5,7 }; int textindices[36] = { 2,1,0, 2,0,3, 2,0,1, 2,3,0, 1,0,3, 1,3,0, 0,1,2, 2,3,0, 2,0,1, 2,3,0, 2,1,0, 3,2,0 }; for(int i =0; i <12;++i) { POLYGON temp ; temp.v[0] = tempindices[i*3+0]; temp.v[1] = tempindices[i*3+1]; temp.v[2] = tempindices[i*3 +2]; temp.text[0] = textindices[i*3+0]; temp.text[1] = textindices[i*3+1]; temp.text[2] = textindices[i*3+2]; temp.state |= OBJECT_HAS_TEXTURE|SHADE_MODEL_CONSTANT; temp.color = RGBA(RGB(128,123,140)); temp.texture = &bitmap; temp.pointlist = &pobj->m_verticesList; temp.texturecoordinatelist = &pobj->m_texturecoordinatesList; pobj->m_PolyGonList.push_back(temp); } pobj->world_pos = POINT3D(-10,0,50); CRenderObject *pobj2 = new CRenderObject(*pobj); pobj2->world_pos = POINT3D(15,0,50); CRenderObject* pobj3 =new CRenderObject(*pobj); pobj3->world_pos = POINT3D(0,10,80); CRenderObject *pobj4 = new CRenderObject(*pobj); pobj4->world_pos = POINT3D(20,-10,100); g_RenderManager.Add(pobj); g_RenderManager.Add(pobj2); g_RenderManager.Add(pobj3); g_RenderManager.Add(pobj4); //Set lights CLight *pLight = new CLight; pLight->InitLight(CLight::kAmbientLight,RGBA(RGB(255,0,0)),Black_Color,Black_Color); g_lights.AddLight(pLight); CLight *pLight2 = new CLight; pLight2->InitLight(CLight::kInfiniteLigtht,Black_Color,RGBA(255,0,0),Black_Color,ZeroVector, Vector4D(-1.0,0.0,1.0)); g_lights.AddLight(pLight2); CLight *pLight3 = new CLight; pLight3->InitLight(CLight::kPointLight,Black_Color,RGBA(0,255,0),Black_Color,Vector4D(0,10,0), ZeroVector,1.0,2.0,4.0); g_lights.AddLight(pLight3); CLight *pLight4 = new CLight; pLight4->InitLight(CLight::kSpotLight,Black_Color,RGBA(255,255,255),Black_Color,Vector4D(0,20,10), Vector4D(0.0,0.0,1.0),0.0,0.01,0.0,30.0,60.0,1.0); g_lights.AddLight(pLight4); g_RenderManager.SetCamera(&g_cam); g_RenderManager.SetLightManager(&g_lights); }
Cube::Cube(Object *parent, const material_t material, const point3d_t dim, const point3d_t p, const point3d_t d) : Shape(parent, material, p, d), dim(dim), top1(this, MATERIAL(COLOR_RED), POINT3D(dim.x/2.0, dim.y/2.0, dim.z/2.0), // (x, y, z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (x, y, -z) POINT3D(-dim.x/2.0, dim.y/2.0, dim.z/2.0)), // (-x, y, z) top2(this, MATERIAL(COLOR_RED), POINT3D(-dim.x/2.0, dim.y/2.0, dim.z/2.0), // (-x, y, z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (x, y, -z) POINT3D(-dim.x/2.0, dim.y/2.0, -dim.z/2.0)), // (-x, y, -z) bottom1(this, MATERIAL(COLOR_RED), POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0), // (-x, -y, z) POINT3D(dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (x, -y, -z) POINT3D(dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (x, -y, z) bottom2(this, MATERIAL(COLOR_RED), POINT3D(-dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (-x, -y, -z) POINT3D(dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (x, -y, -z) POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (-x. -y. z) left1(this, MATERIAL(COLOR_BLUE), POINT3D(-dim.x/2.0, dim.y/2.0, dim.z/2.0), // (-x, y, z) POINT3D(-dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (-x, y, -z) POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (-x, -y, z) left2(this, MATERIAL(COLOR_BLUE), POINT3D(-dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (-x, y, -z) POINT3D(-dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (-x, -y, -z) POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (-x, -y, z) right1(this, MATERIAL(COLOR_BLUE), POINT3D(dim.x/2.0, -dim.y/2.0, dim.z/2.0), // (x, -y, z) POINT3D(dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (x, -y, -z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0)), // (x, y, -z) right2(this, MATERIAL(COLOR_BLUE), POINT3D(dim.x/2.0, -dim.y/2.0, dim.z/2.0), // (x, -y, z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (x, y, -z) POINT3D(dim.x/2.0, dim.y/2.0, dim.z/2.0)), // (x, y, z) front1(this, MATERIAL(COLOR_GREEN), POINT3D(dim.x/2.0, dim.y/2.0, dim.z/2.0), // (x, y, z) POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0), // (-x, -y ,z) POINT3D(dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (x, -y, z) front2(this, MATERIAL(COLOR_GREEN), POINT3D(dim.x/2.0, dim.y/2.0, dim.z/2.0), // (x, y, z) POINT3D(-dim.x/2.0, dim.y/2.0, dim.z/2.0), // (-x, y, z) POINT3D(-dim.x/2.0, -dim.y/2.0, dim.z/2.0)), // (-x, -y, z) back1(this, MATERIAL(COLOR_GREEN), POINT3D(dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (x, -y, -z) POINT3D(-dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (-x, -y, -z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0)), // (x, y, -z) back2(this, MATERIAL(COLOR_GREEN), POINT3D(-dim.x/2.0, -dim.y/2.0, -dim.z/2.0), // (-x, -y, -z) POINT3D(-dim.x/2.0, dim.y/2.0, -dim.z/2.0), // (-x, y, -z) POINT3D(dim.x/2.0, dim.y/2.0, -dim.z/2.0)) // (x, y, -z) { }
color_t Shape::phong(const point3d_t p, const point2d_t tc, const point3d_t n, const ray_t *ray) const { const World *world = this->world(); color_t texture = material.texture == 0 ? material.color : material.texture(tc); if(!world) return texture; boost::numeric::ublas::vector<double> color = vector3d(0.0, 0.0, 0.0); boost::numeric::ublas::vector<double> vn = vector3d(n.x, n.y, n.z); boost::numeric::ublas::vector<double> vv = vector3d(-ray->d.x, -ray->d.y, -ray->d.z); double dvn = dot_prod(vv, vn); double nit = 1.0 / material.n; if (dvn < 0.0) { nit = material.n / 1.0; vn = -vn; dvn = dot_prod(vv, vn); } boost::numeric::ublas::vector<double> texture_color = vector3d(texture.r, texture.g, texture.b); boost::numeric::ublas::vector<double> specular_color = vector3d(material.specular.r, material.specular.g, material.specular.b); boost::numeric::ublas::vector<double> ambient_color = vector3d(world->ambient.r, world->ambient.g, world->ambient.b); // ambient color += material.ka * vector3d(ambient_color(0) * texture_color(0), ambient_color(1) * texture_color(1), ambient_color(2) * texture_color(2)); double kr = material.kr; // transmission if(ray->ttl > 0 && material.kt > 0.0 && material.n > 0.0) { double l = 1.0 + nit*nit*(dvn*dvn - 1.0); if(l >= 0.0) { boost::numeric::ublas::vector<double> d = -nit * vv; d += (nit*dvn - sqrt(l)) * vn; d = normalize(d); ray_t transmission_ray; transmission_ray.p = p; transmission_ray.d = POINT3D(d(0), d(1), d(2)); transmission_ray.near = 0.000000001; transmission_ray.far = DOUBLE_INF; transmission_ray.ttl = ray->ttl-1; color_t transmission_color = world->trace(&transmission_ray); color += material.kt * vector3d(transmission_color.r, transmission_color.g, transmission_color.b); } else kr += material.kt; } // reflection if(ray->ttl > 0 && kr > 0.0) { boost::numeric::ublas::vector<double> vr = 2.0 * dot_prod(vv, vn) * vn - vv; ray_t reflection_ray; reflection_ray.p = p; reflection_ray.d = POINT3D(vr(0), vr(1), vr(2)); reflection_ray.near = 0.000001; reflection_ray.far = DOUBLE_INF; reflection_ray.ttl = ray->ttl-1; color_t reflection_color = world->trace(&reflection_ray); color += kr * vector3d(reflection_color.r, reflection_color.g, reflection_color.b); } // lights for(std::vector<Light*>::const_iterator i = world->lights.begin(); i != world->lights.end(); i++) { // shadow ray point3d_t s = (*i)->getSource(); boost::numeric::ublas::vector<double> vs = vector3d(s.x - p.x, s.y - p.y, s.z - p.z); double light_distance = sqrt(dot_prod(vs, vs)); vs = normalize(vs); ray_t shadow_ray; shadow_ray.p = p; shadow_ray.d = POINT3D(vs(0), vs(1), vs(2)); shadow_ray.near = 0.00000001; shadow_ray.far = DOUBLE_INF; shadow_ray.ttl = 1; if(world->hit(&shadow_ray).t < light_distance) continue; light_t light = (*i)->getLight(); boost::numeric::ublas::vector<double> light_color = vector3d(light.color.r, light.color.g, light.color.b); // diffuse color += material.kd * dot_prod(vs, vn) * vector3d(light_color(0) * texture_color(0), light_color(1) * texture_color(1), light_color(2) * texture_color(2)); // specular boost::numeric::ublas::vector<double> vr = 2.0 * dot_prod(vs, vn) * vn - vs; double vra = dot_prod(vr, vv); if(vra >= 0.0) color += material.ks * pow(vra, material.ke) * vector3d(light_color(0) * specular_color(0), light_color(1) * specular_color(1), light_color(2) * specular_color(2)); } // keep color in range if(color(0) > 1.0) color(0) = 1.0; if(color(0) < 0.0) color(0) = 0.0; if(color(1) > 1.0) color(1) = 1.0; if(color(1) < 0.0) color(1) = 0.0; if(color(2) > 1.0) color(2) = 1.0; if(color(2) < 0.0) color(2) = 0.0; return COLOR(color(0), color(1), color(2)); }
inline POINT3D operator -(POINT3D a,POINT3D b){ return POINT3D(a.x-b.x,a.y-b.y,a.z-b.z); }
inline POINT3D operator ^(POINT3D a,POINT3D b){ return POINT3D(a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x); }