diffuse反射はPhongの反射モデルの一部で、ADSのDの部分。やってることは面法線と光のベクトルの内積を光の強さとしているといっていい(だろう。たぶん。)。ベクトルの内積は、その角度が小さくなると大きくなり、角度が大きくなると小さくなる。
ただこの現象を文章で説明されてもぱっと思い浮かばないので可視化する
これは面ごとにEmissionで色を付けている。
角度が小さいほど明るい色になっている。
実行すると、光源の位置に該当する球と、対象物に対する球、光源から物体の方向のArrowと各面の面法線、その位置からの光源へのベクトルが生成される。
import bpy import mathutils import numpy import math # @brief オブジェクトを指定座標へ向けるための回転軸と回転角を計算する # @param [in] オブジェクト # @param [in] 向ける方向(座標) # @return 回転軸,回転角(ラジアン) def calc_lookat_param(obj,lookat): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) #print(localZ) va = mathutils.Vector(lookat) - obj.location vb = mathutils.Vector(localZ) va.normalize() vb.normalize() # 外積 axis = mathutils.Vector.cross(va,vb) axis.normalize() # 内積 th = mathutils.Vector.dot(va,vb) # 角度算出 rad = -math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad # @brief 任意軸回転 # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし # @sa https://www.study.suzulang.com/bpy-calculation/bpy-arbitrary-axis-rotation def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat def fix_arrow_dir(aobj,_to): # 方向を一致させるためのパラメータ取得 axis , rad = calc_lookat_param(aobj,_to) # 回転 rotate_object(aobj,axis,rad) def fix_arrow_fromto(aobj,_from,_to): aobj.location=_from fix_arrow_dir(aobj,_to) aobj.location=_from # _from → _to の距離を計算 objlen = ( _from - _to ).length # 矢印の長さ==スケール aobj.scale[0] = objlen aobj.scale[1] = objlen aobj.scale[2] = objlen # @brief オブジェクトの指定したpolygonの法線を取得 # @param [in] obj オブジェクト # @param [in] polyIndex ポリゴン番号 # @return 法線(グローバル座標系) def get_polygon_normal(obj,polyIndex): # オブジェクトの行列を取得 local44 = obj.matrix_world ll_inv = local44.inverted() ll_inv_norm = ll_inv.transposed().to_3x3() vn = obj.data.polygons[polyIndex].normal return ll_inv_norm @ vn # @brief _fromから_toを指すEmptyArrowを作成 # @param [in] 矢印の根元の座標 # @param [in] 矢印の先端の座標 # @return 作成したオブジェクト def make_empty_arrow(_from,_to): # 新しいEmpty ObjectのArrowを作成 bpy.ops.object.empty_add(type="SINGLE_ARROW",location=_from) aobj = bpy.context.object fix_arrow_fromto(aobj,_from,_to) return aobj ##################################################### ##################################################### # 矢印一個のオブジェクト。 class Arrow: _from = mathutils.Vector((0,0,0)) _to = mathutils.Vector((0,0,0)) _object = None def __init__(self,name): exist = bpy.context.scene.objects.get(name) if exist is None: bpy.ops.object.empty_add(type="SINGLE_ARROW",location=(0,0,0)) self._object = bpy.context.object self._object.name = name else: self._object = exist #obj = bpy.context.active_object # オブジェクトのローカルのZ軸のベクトルを取得 mat = self._object.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) _to = localZ + self._object.location def get_direction(self,normalize): v = _to - _from if normalize == True: v.normalize return v def get_arrow(self,normalize): pos = get_direction(normalize) __to = _from+pos return _from , __to def set_arrow_from_to(self,_from,_to): fix_arrow_fromto(self._object,_from,_to) self._from = _from self._to = _to def get_vnorm(self): v = self._to - self._from v.normalize() return v def set_length(self,length): self._object.scale[0]=length self._object.scale[1]=length self._object.scale[2]=length ##################################################### ##################################################### class TheLight: light = None arrowToTarget = None def __init__(self,_pos): self.light = bpy.context.scene.objects.get('TheLight') if self.light is None: # 新しいEmpty ObjectのArrowを作成 bpy.ops.mesh.primitive_ico_sphere_add(location=_pos,radius =0.3) self.light = bpy.context.object self.light.name = 'TheLight' def update(self,target): self.arrowToTarget = Arrow('LightToTarget') self.arrowToTarget.set_arrow_from_to( self.light.location, target.target.location ) self.arrowToTarget.set_length(2) def get_location(self): return self.light.location ############################################### ############################################### # 各面ごとに、面法線と光源の方向を示すための矢印を定義する class Plane: normal = None material_name = None plane_index = 0 toLightArrow = None normalArrow_name = "" Ld = 0.0 def __init__(self,obj,pindex): self.normalArrow_name = 'PlaneNormal-' + str(pindex) self.normal = Arrow(self.normalArrow_name) mat_name = "mat" + str(pindex) self.material_name = mat_name mat = bpy.data.materials.get(mat_name) # マテリアルがなければ作成する if mat is None: mat = bpy.data.materials.new(mat_name) # マテリアルをオブジェクトに設定する obj.data.materials.append(mat) mat.use_nodes = True self.material_name = mat_name self.plane_index = pindex obj.data.polygons[pindex].material_index = pindex def update(self,origin,dir,light): self.normal.set_arrow_from_to( origin, origin+dir) # direction ldir = light.get_location()-origin ldir.normalize() # 面に光が当たったときの明るさを計算 # 面法線とライト方向へのベクトルの内積 Kd = 1.0 I = 1.0 self.Ld = Kd*I*numpy.dot( self.normal.get_vnorm(), -light.arrowToTarget.get_vnorm() ) if self.Ld < 0: self.Ld = 0 else: # arrow-to-line lightarrowname = self.normalArrow_name +'-tolight' self.toLightArrow = Arrow(lightarrowname) tolight = origin-light.arrowToTarget.get_vnorm() self.toLightArrow.set_arrow_from_to(origin,tolight) self.toLightArrow.set_length(0.3) self.normal.set_length(self.Ld*0.5) def set_material(self,polyindex): material =bpy.data.materials.get(self.material_name) material_output = material.node_tree.nodes.get('Material Output') #emission = new_mat.node_tree.nodes.get('ShaderNodeEmission') emission = material.node_tree.nodes.get('Emission') if emission is None: print( 'Emission:',emission ) emission = material.node_tree.nodes.new('ShaderNodeEmission') material.node_tree.links.new(material_output.inputs[0], emission.outputs[0]) ntree = material.node_tree#.new('ShaderNodeEmission') print( self.Ld ) material.node_tree.nodes["Emission"].inputs["Color"].default_value[0] = 0.5 * self.Ld material.node_tree.nodes["Emission"].inputs["Color"].default_value[1] = 0.9 * self.Ld material.node_tree.nodes["Emission"].inputs["Color"].default_value[2] = 0.8 * self.Ld ############################################### ############################################### class TheTarget: target = None # 光が当たるオブジェクト planes = [] # 各面の設定を面ごとに入れる def create_planes(self): mesh = self.target.data k = len(mesh.polygons) # 全てのポリゴンに対するループ for i in range(0,k): self.planes.append( Plane(self.target,i) ) def update(self,light): mesh = self.target.data vcoords = mesh.vertices # オブジェクトの行列を取得 local44 = self.target.matrix_world k = len(self.planes) for i in range(0,k): n = get_polygon_normal(self.target,i) ## origin of polygon grav = mathutils.Vector((0,0,0)) for v in mesh.polygons[i].vertices: # v はpolyの各頂点のID grav += vcoords[ v ].co # 頂点座標の合計を頂点数で割る grav /= len(mesh.polygons[i].vertices) origin = local44 @ grav self.planes[i].update( origin, n, light) self.planes[i].set_material(self.target) def __init__(self,_pos): self.target = bpy.context.scene.objects.get('TargetMesh') if self.target is None: #bpy.ops.mesh.primitive_plane_add(location=_pos) bpy.ops.mesh.primitive_ico_sphere_add( location=_pos, radius =1.0 ) self.target = bpy.context.object self.target.name = 'TargetMesh' self.create_planes() ############################################### ############################################### ############################################### def my_function(): target = TheTarget( (-1,2,1) ) thelight = TheLight( (1,5,5) ) thelight.update(target) target.update(thelight) ############################################### ############################################### ############################################### my_function()
度々使う自前の画像読み込み関数。メモリ確保を関数側にやらせると呼び出し側の処理が煩雑になるので、いっそのことメモリ確保関数を渡してしまうことにした。
#pragma once #include <stdio.h> #include <string> #include <functional> #pragma warning(disable:4996) //! @brief PPM(RGB各1byte,カラー,テキスト)を読み込む //! @param [in] fname //! @param [out] width 画像幅 //! @param [out] height 画像高さ //! @param [out] vmax 最大値 //! @param [in] memalloc メモリ確保用の関数 //! @retval true 読み込み成功 //! @retval false 読み込み失敗 bool ppmP3_read( const char* const fname, int *width, int *height, int *vmax, std::function<unsigned char*(const size_t pixelcount)> memalloc ) { *width = -1; *height = -1; *vmax = -1; FILE* fp; fp = fopen(fname, "rb"); char tmp[2048]; char c; while (c = fgetc(fp)) { if (isspace(c)) continue; if (c == 'P') { //フォーマットを特定する c = fgetc(fp) - '0'; if (c != 3) { fclose(fp); return false; } continue; } if (c == '#') { //コメントを読み飛ばす while (c != '\r' && c != '\n') c = fgetc(fp); continue; } //幅取得 if (*width < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *width = atoi(tmp); break; } } continue; } //高さ取得 if (*height < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *height = atoi(tmp); break; } } continue; } //最大値(白)取得 if (*vmax < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *vmax = atoi(tmp); break; } } break; } else { break; } } if (*width < 0 || *height < 0 || *vmax < 0) { return false; }
//////////////////////////////////////// //メモリ確保 size_t count = *width* *height; size_t memsize = count*3; unsigned char* pimg = memalloc(count); ////////////////////////////////////////
int read = 0; char text[100]; int index = 0; //メモリサイズ分まで読み込む while (read < memsize) { int c = fgetc(fp);//一文字取得 if (c == EOF)//入力できなかったらループから出る break; //数字ならストックする if (isdigit(c)) { text[index] = (char)c; index++; } else { //数字以外が入力されたら、 //ストックした数字を数値化 //ただし数字が何もストックされていないなら //なにもしない if (index) { text[index] = '\0'; pimg[read] = atoi(text); read++; index = 0; } } } return read == memsize; }
#include <vector> // https://www.study.suzulang.com/2dcg-functions/nbyte-data-type #include "NByteData.hpp" // 以下をベースに変更: // https://www.study.suzulang.com/cppppm-readerwriter/ppm-p3-reader-cpp #include "ppmP3_read.hpp" ///////////////////////////////////////////////////// void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { using PixelT = NByteData<3>; std::vector<PixelT> mem; // 画像読み込み int width; int height; int vmax; ppmP3_read( "C:\\test\\p.ppm", &width, &height, &vmax, [&mem](const size_t pixelcount) { mem.resize(pixelcount); return (unsigned char*)&mem[0]; } ); // 色を反転 for(auto& p : mem){ p.data()[0] = 255 - p.data()[0]; p.data()[1] = 255 - p.data()[1]; p.data()[2] = 255 - p.data()[2]; } ///////////////////////////////// ///////////////////////////////// ppmP3_write( "C:\\test\\q.ppm", width, height, (unsigned char*)&mem[0], 255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
C++の例が少ないので載せておく。
#include <Open3D/IO/ClassIO/TriangleMeshIO.h> #include<Open3D/Geometry/TriangleMesh.h> // 表示に必要 #include<Open3D/Visualization/Visualizer/Visualizer.h> // 表示に必要 #pragma comment(lib,"glew.lib") #pragma comment(lib,"glfw3.lib") #pragma comment(lib,"opengl32.lib") int main() { auto mesh = std::make_shared<open3d::geometry::TriangleMesh>(); open3d::io::ReadTriangleMeshFromPLY("bunny.ply",*mesh,true); //これがないとライトで照らせない mesh->ComputeTriangleNormals(true); open3d::visualization::Visualizer vis; vis.CreateVisualizerWindow("O3D"); //CreateVisualizerWindowを呼び出してからオブジェクトを登録 vis.AddGeometry(mesh); Eigen::Vector3d center = mesh->GetCenter(); // Change view open3d::visualization::ViewControl& view_control = vis.GetViewControl(); auto view_params = open3d::visualization::ViewParameters(); view_control.ConvertToViewParameters(view_params); view_params.front_ = Eigen::Vector3d(0, -1, 0); view_params.lookat_ = center; view_params.up_ = Eigen::Vector3d(0, 0, 1); //view_params.zoom_ = 1.0; view_control.ConvertFromViewParameters(view_params); // PollEventsはウィンドウが閉じられるときにfalseを返す while (vis.PollEvents() == true) { vis.UpdateGeometry(); vis.UpdateRender(); } vis.DestroyVisualizerWindow(); }
試験的なプログラムを書くだけでいちいちモデルを読み込むライブラリを使うのが面倒なので、Blender PythonでC++OpenGLのコードをprintする。まだやってなかったっけという感じなんだがやっていたら申し訳ない。
import bpy import sys def as_cpp_array(outputto): obj = bpy.context.active_object mesh = obj.data pvecname = "points" # 頂点データの配列の変数名 vnvecname = "vnorms" # 頂点法線の配列の変数名 fvecname = "faces" # 面データの配列の変数名 fnvecname = "fnorms" # 面法線の配列の変数名 print("/////////////////////////////////////") if not pvecname is None: print("//std::vector<std::array<float, 3> > ",pvecname,";",file=outputto) if not vnvecname is None: print("//std::vector<std::array<float, 3> > ",vnvecname,";",file=outputto) if not fvecname is None: print("//std::vector<std::array<int, 3> > ",fvecname,";",file=outputto) if not fnvecname is None: print("//std::vector<std::array<float, 3> > ",fnvecname,";",file=outputto) if not pvecname is None: for v in mesh.vertices: print( pvecname + ".push_back({", v.co.x,",", v.co.y,",", v.co.z, "});", file=outputto ) if not vnvecname is None: for v in mesh.vertices: print( vnvecname + ".push_back({", v.normal.x,",", v.normal.y,",", v.normal.z, "});", file=outputto ) if not fvecname is None: for f in mesh.polygons: print( fvecname + ".push_back({", f.vertices[0],",", f.vertices[1],",", f.vertices[2], "});", file=outputto ) if not fnvecname is None: for f in mesh.polygons: print( fnvecname + ".push_back({", f.normal.x,",", f.normal.y,",", f.normal.z, "});", file=outputto ) as_cpp_array(sys.stdout)
これをactive_objectに対して走らせると、例えば以下のような結果が得られる。
/////////////////////////////////////
//std::vector<std::array<float, 3> > points ;
//std::vector<std::array<float, 3> > vnorms ;
//std::vector<std::array<int, 3> > faces ;
//std::vector<std::array<float, 3> > fnorms ;
points.push_back({ 0.0 , 0.0 , -0.57135009765625 });
points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 });
points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 });
points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 });
points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 });
points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 });
points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 });
points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 });
points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 });
points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 });
points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 });
points.push_back({ 0.0 , 0.0 , 0.57135009765625 });
vnorms.push_back({ 0.0 , 0.0 , -1.0 });
vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 });
vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 });
vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 });
vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 });
vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 });
vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 });
vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 });
vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 });
vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 });
vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 });
vnorms.push_back({ 0.0 , 0.0 , 1.0 });
faces.push_back({ 0 , 1 , 2 });
faces.push_back({ 1 , 0 , 5 });
faces.push_back({ 0 , 2 , 3 });
faces.push_back({ 0 , 3 , 4 });
faces.push_back({ 0 , 4 , 5 });
faces.push_back({ 1 , 5 , 10 });
faces.push_back({ 2 , 1 , 6 });
faces.push_back({ 3 , 2 , 7 });
faces.push_back({ 4 , 3 , 8 });
faces.push_back({ 5 , 4 , 9 });
faces.push_back({ 1 , 10 , 6 });
faces.push_back({ 2 , 6 , 7 });
faces.push_back({ 3 , 7 , 8 });
faces.push_back({ 4 , 8 , 9 });
faces.push_back({ 5 , 9 , 10 });
faces.push_back({ 6 , 10 , 11 });
faces.push_back({ 7 , 6 , 11 });
faces.push_back({ 8 , 7 , 11 });
faces.push_back({ 9 , 8 , 11 });
faces.push_back({ 10 , 9 , 11 });
fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 });
fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 });
fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 });
fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 });
fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 });
fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 });
fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 });
fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 });
fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 });
fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 });
fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 });
fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 });
fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 });
fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 });
fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 });
fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 });
fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 });
fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 });
fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 });
fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 });
これをC++で書いたOpenGLの表示コードに貼り付ける。
#include <iostream> #include <cstdlib> #include <Windows.h> #include <gl/GL.h> #include <gl/glfw/glfw3.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"glfw3.lib") #include <gl/GLU.h> #pragma comment(lib,"glu32.lib") #include<vector> #include<array>
struct d3item { std::vector<std::array<float, 3> > points; std::vector<std::array<float, 3> > vnorms; std::vector<std::array<int, 3> > faces; std::vector<std::array<float, 3> > fnorms; public: void init() { points.push_back({ 0.0 , 0.0 , -0.57135009765625 }); points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 }); points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 }); points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 }); points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 }); points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 }); points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 }); points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 }); points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 }); points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 }); points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 }); points.push_back({ 0.0 , 0.0 , 0.57135009765625 }); vnorms.push_back({ 0.0 , 0.0 , -1.0 }); vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 }); vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 }); vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 }); vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 }); vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 }); vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 }); vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 }); vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 }); vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 }); vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 }); vnorms.push_back({ 0.0 , 0.0 , 1.0 }); faces.push_back({ 0 , 1 , 2 }); faces.push_back({ 1 , 0 , 5 }); faces.push_back({ 0 , 2 , 3 }); faces.push_back({ 0 , 3 , 4 }); faces.push_back({ 0 , 4 , 5 }); faces.push_back({ 1 , 5 , 10 }); faces.push_back({ 2 , 1 , 6 }); faces.push_back({ 3 , 2 , 7 }); faces.push_back({ 4 , 3 , 8 }); faces.push_back({ 5 , 4 , 9 }); faces.push_back({ 1 , 10 , 6 }); faces.push_back({ 2 , 6 , 7 }); faces.push_back({ 3 , 7 , 8 }); faces.push_back({ 4 , 8 , 9 }); faces.push_back({ 5 , 9 , 10 }); faces.push_back({ 6 , 10 , 11 }); faces.push_back({ 7 , 6 , 11 }); faces.push_back({ 8 , 7 , 11 }); faces.push_back({ 9 , 8 , 11 }); faces.push_back({ 10 , 9 , 11 }); fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 }); fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 }); fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 }); fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 }); fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 }); fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 }); fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 }); fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 }); fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 }); fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 }); fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 }); fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 }); fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 }); fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 }); fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 }); fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 }); fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 }); fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 }); fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 }); fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 }); } void draw() { auto color = std::array<float, 4>{1.0, 0, 0, 1}; glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color.data()); glColor3d(1, 1, 1); glBegin(GL_TRIANGLES); for (size_t f = 0; f < faces.size(); f++) { //面法線 glNormal3fv(fnorms[f].data()); size_t p0 = faces[f][0]; size_t p1 = faces[f][1]; size_t p2 = faces[f][2]; //頂点法線 //glNormal3fv(vnorms[p0].data()); glVertex3fv(points[p0].data()); //頂点法線 //glNormal3fv(vnorms[p1].data()); glVertex3fv(points[p1].data()); //頂点法線 //glNormal3fv(vnorms[p2].data()); glVertex3fv(points[p2].data()); } glEnd(); } };
int main() { //////////////////////////////////////////////////////////////////////////////// // GLFW の初期化 if (glfwInit() == GL_FALSE) { // 初期化に失敗したら終了 return 1; } //////////////////////////////////////////////////////////////////////////////// // ウィンドウを作成 GLFWwindow* window = glfwCreateWindow( 400, //width 400, //height "window title",//title NULL, //monitor NULL //share ); //////////////////////////////////////////////////////////////////////////////// // ウィンドウを作成できなければ終了 if (window == nullptr) { glfwTerminate(); return 1; } d3item item; item.init(); glfwMakeContextCurrent(window); while (glfwWindowShouldClose(window) == GL_FALSE) { int width, height; glfwGetFramebufferSize(window, &width, &height); const GLfloat lightPos[] = { -3 , 0 , 0 , 1 }; const GLfloat lightCol[] = { 1 , 1 , 1 , 1 }; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol); glLightfv(GL_LIGHT0, GL_POSITION, lightPos); glEnable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); ///////////////////// // 描画 glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPerspective(45, width / (double)height, 0.01, 10); { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); static int angle = 0; glTranslated(0, 0, -2); glRotated(angle++, 1, 1, 1); item.draw(); glPopMatrix(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); // ///////////////////// //glFlush(); glfwSwapBuffers(window); // イベント取得 glfwWaitEvents(); } glfwTerminate(); }
特にアドオン化したときは、コンソールにメッセージが出るのはユーザーフレンドリーではない。
以下のようにメッセージボックス表示関数を作れる。
見るからにポップアップウィンドウ表示用関数を使った方法なので、応用が利きそうではある。
import bpy
# @brief GUIでメッセージを表示する # @param [in] message メッセージ本文 # @param [in] tilte メッセージボックスのタイトル # @param [in] icon 使用するアイコン def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): def draw(self, context): self.layout.label(text=message) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
################################ # 使用例 obj = bpy.context.active_object if not obj.type == 'MESH': # メッシュが選択されていなかったらエラーを表示 ShowMessageBox("object is not a mesh")
実行するとマウスの位置にメッセージボックスが表示される。
アイコンは、以下に使用できる一覧があるが、メッセージボックスとして使うのはせいぜい'NONE', 'QUESTION', 'ERROR', 'CANCEL','INFO'くらいだと思う。
基本的に以下そのまま。
メッシュの面はmesh.polygonsに入っている。マテリアルは各面のmaterial_indexにindexの形で指定できるが、マテリアルのインデクスの取得はbpy.data.materials.findで行う。
import bpy # カラーテーブルを作っておく ctable = [] ctable.append( (0,0,1,1) ) ctable.append( (0,1,0,1) ) ctable.append( (0,1,1,1) ) ctable.append( (1,0,0,1) ) ctable.append( (1,0,1,1) ) ctable.append( (1,1,0,1) ) obj = bpy.context.active_object mesh = obj.data i = 0 for face in mesh.polygons: # 全ての面について処理する # マテリアルの存在確認 mat_name = "mat" + str(i) material = bpy.data.materials.get(mat_name) # マテリアルがなければ作成する if material is None: material = bpy.data.materials.new(mat_name) # マテリアルをオブジェクトに設定する obj.data.materials.append(material) material.use_nodes = True # 色を設定 pBSDF = material.node_tree.nodes["Principled BSDF"] pBSDF.inputs[0].default_value = ctable[i] # マテリアルのIndexを取得する matindex = bpy.data.materials.find(mat_name) # 面に使用するマテリアルを設定する face.material_index = matindex i += 1
参考:
https://docs.blender.org/api/current/bpy.msgbus.html
まず、Locationから位置変更した場合にメッセージを表示させる
import bpy #https://docs.blender.org/api/current/bpy.msgbus.html # Any Python object can act as the subscription's owner. owner = object() # この関数が呼び出される def msgbus_callback(*args): print("Something changed!", args) # locationを指定して、位置移動の時に呼び出されるようにする # ただしサイドバーのLocationの操作のみ対応 # [G]などで移動した場合は反応しない subscribe_to = bpy.context.object.location bpy.msgbus.subscribe_rna( key=subscribe_to, owner=owner, args=("hello",), # タプルなので引数一つの場合は(a,)のような形にしないといけない notify=msgbus_callback, )
さて回転の場合なのだが、
などとやってもエラーになる。
意訳 bpy.context.objectにrotationなんてない
ではどうするかというと、次のようにしてbpy.context.objectでつかえそうなものを調べる
import bpy li = dir( bpy.context.object ) for i in li: print(i)
以下出力。大量に出てくるが、locationのほかに、rotation_eulerなどを見つけられる。
__doc__
__module__
__slots__
active_material
active_material_index
active_shape_key
...
lineart
local_view_get
local_view_set
location
lock_location
lock_rotation
...
rotation_axis_angle
rotation_euler
rotation_mode
rotation_quaternion
scale
select_get
select_set
...
user_remap
users
users_collection
users_scene
vertex_groups
visible_get
visible_in_viewport_get
というわけで回転時に動かすにはbpy.context.object.rotation_eulerを指定する。
import bpy #https://docs.blender.org/api/current/bpy.msgbus.html # Any Python object can act as the subscription's owner. owner = object() # この関数が呼び出される def msgbus_callback(*args): print("Something changed!", args)
# rotation_eulerを指定して、回転の時に呼び出されるようにする # ただしサイドバーのRotationの操作のみ対応 # [R]などで移動した場合は反応しない subscribe_to = bpy.context.object.rotation_euler
bpy.msgbus.subscribe_rna( key=subscribe_to, owner=owner, args=("rotation-euler",), # タプルなので引数一つの場合は(a,)のような形にしないといけない notify=msgbus_callback, )
from → to を指すempty arrowを追加する。以下では、現在のアクティブオブジェクトのoriginから3D cursorへ矢印を作成する
import bpy import math import numpy import mathutils # @brief オブジェクトを指定座標へ向けるための回転軸と回転角を計算する # @param [in] オブジェクト # @param [in] 向ける方向(座標) # @return 回転軸,回転角(ラジアン) def calc_lookat_param(obj,lookat): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) print(localZ) va = mathutils.Vector(lookat) - obj.location vb = mathutils.Vector(localZ) va.normalize() vb.normalize() # 外積 axis = mathutils.Vector.cross(va,vb) axis.normalize() # 内積 th = mathutils.Vector.dot(va,vb) # 角度算出 rad = -math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad # @brief 任意軸回転 # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし # @sa https://www.study.suzulang.com/bpy-calculation/bpy-arbitrary-axis-rotation def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
# @brief _fromから_toを指すEmptyArrowを作成 # @param [in] 矢印の根元の座標 # @param [in] 矢印の先端の座標 # @return 作成したオブジェクト def make_empty_arrow(_from,_to): # 新しいEmpty ObjectのArrowを作成 bpy.ops.object.empty_add(type="SINGLE_ARROW",location=_from) aobj = bpy.context.object # 方向を一致させるためのパラメータ取得 axis , rad = calc_lookat_param(aobj,_to) # 回転 rotate_object(aobj,axis,rad) # _from → _to の距離を計算 objlen = ( _from - _to ).length # 矢印の長さ==スケール aobj.scale[0] = objlen aobj.scale[1] = objlen aobj.scale[2] = objlen return aobj
# 使用例 obj = bpy.context.active_object arrow = make_empty_arrow( obj.location, bpy.context.scene.cursor.location ) print(arrow.name)
import bpy import bmesh import mathutils import numpy import math
# @brief オブジェクトの指定したpolygonの法線を取得 # @param [in] obj オブジェクト # @param [in] polyIndex ポリゴン番号 # @return 法線(グローバル座標系) def got_polygon_normal(obj,polyIndex): # オブジェクトの行列を取得 local44 = obj.matrix_world ll_inv = local44.inverted() ll_inv_norm = ll_inv.transposed().to_3x3() vn = obj.data.polygons[polyIndex].normal return ll_inv_norm @ vn
# @brief 任意軸回転 # @sa https://www.study.suzulang.com/bpy/bpy-arbitrary-axis-rotation # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
# @brief (0,0,1)のベクトルを法線方向に回転するための回転軸と回転角度を求める # @param [in] the_normal 今回使用する法線 # @return 回転軸,回転角(ラジアン) def calc_to_normal(the_normal): Zvec = mathutils.Vector((0,0,1)) vn = the_normal onorm = mathutils.Vector(vn) onorm.normalize() # 外積 axis = mathutils.Vector.cross(Zvec,onorm) axis.normalize() # 内積 th = mathutils.Vector.dot(Zvec,onorm) # 角度算出 rad = math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad
# @brief オブジェクトの0番目のポリゴンの法線を計算し、empty arrowで表示する # @param [in] target 目的のオブジェクト def disp_polygon_normal(target): tnormal = got_polygon_normal(target,0) axis , rad = calc_to_normal(tnormal) bpy.ops.object.empty_add(type="SINGLE_ARROW") arrow = bpy.context.active_object rotate_object(arrow,axis,rad) arrow.location = target.location
# 実行例 target = bpy.context.active_object disp_polygon_normal(target)
以前のコードが汎用性に欠けるというか使いにくかったのでもう一度書き直した。
import bpy import math import mathutils
#################################################################### #################################################################### #################################################################### #################################################################### # @brief 任意軸回転 # @sa https://www.study.suzulang.com/bpy/bpy-arbitrary-axis-rotation # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
#################################################################### #################################################################### #################################################################### #################################################################### #################################################################### # @brief 指定した位置に矢印を作成 # @param [in] tail 矢印の根本 # @param [in] head 矢印の頭 # @param [in] body_radius 矢印根元の半径 # @return 作成した矢印のオブジェクト def create_arrow(tail,head,body_radius): ########################################### ########################################### # tail->head の長さと方向を計算 avec = head - tail aveclen = avec.length avec.normalize() ########################################### ########################################### # 回転軸と回転量 zvec = mathutils.Vector([ 0.0 , 0.0 , 1.0]) zvec.normalize() rotvec = mathutils.Vector.cross(zvec,avec) rotvec.normalize() rotang = math.acos(mathutils.Vector.dot(zvec,avec) ) ########################################### ########################################### # 各パラメータ計算 # bodyとheadの長さの比率 head_ratio = 0.3 # 頭部長さ変更 head_length = aveclen * head_ratio # 頭部が長くなりすぎないように調整 if head_length > 0.3*4: head_length = 0.3*4 body_length = aveclen - head_length # 矢印の頭部分の付け根の大きさ(本体に対する倍率) head_root_ratio = 2.0 ########################################### ########################################### # サークル追加 # 半径は body_radius bpy.ops.mesh.primitive_circle_add( location=tail, radius=body_radius ) myarrow = bpy.context.object # 作成したサークルを回転して目標へ向ける rotate_object(myarrow,rotvec,rotang) ####################### # サークルを面にする bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.fill() ####################### # step 1 - 押し出し # 押し出し量は 矢印全体の長さ×比率 bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":avec*body_length } ) ####################### # step 2 -| 矢印頭部部分付け根拡大 bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":(0,0,0)} ) bpy.ops.transform.resize( value=( head_root_ratio, head_root_ratio, head_root_ratio) ) ####################### # step 3 -|= 矢印頭部部分押し出し bpy.ops.mesh.extrude_region_move( TRANSFORM_OT_translate={"value":avec*head_length} ) ####################### # step 4 -|> 矢印を閉じる bpy.ops.transform.resize(value=(0,0,0) ) bpy.ops.mesh.remove_doubles() bpy.ops.object.mode_set(mode='OBJECT') return myarrow
################################ #実行例 t = mathutils.Vector((1,2,1))#始点 h = mathutils.Vector((-1,2,3))#先端 arrow = create_arrow(t,h,0.1) arrow.name = "my-arrow" print (arrow)