Shade3D 公式

12 Bezier Patch [ Shade Labo ]


#1

12 - 1 Bezier Patch と Shade Bezier 曲面


bezier line が 4 つの制御点で定義されたように、bezier surface は 4 x 4 = 16 個の制御点で定義され、これを bezier patch と呼びます。

     [ fig 12 - 1 ]


     [ fig 12 - 2 ]


一方 Shade の bezier 曲面は U, V 各方向に 2 つずつ、計4本の bezier line で構成され、その制御点総数は12個です。

bezier patch 16個中、中央の4つについては、定義されていません。

16個もの変数を楽に制御できるようなユーザーインターフェースはほとんど無理であり、Shade では [ fig 12 - 1 ] の A, B, C, D 4つの制御点を他の制御点によって決定されるようにしています。

つまり、16個の独立変数の内、4つが従属変数として表されるよう別途定義し、12個の独立変数で曲面を定義するように変更している ということです。

また、こうすることで 2つの bezier 曲面が滑らかに連続するようにセットするのが簡単になります。


#2

12 - 2   4つの従属変数


Shade では [ fig 12 - 1 ] の制御点 A の座標は 3 つの座標値 P, H, h を用いて次のように定義されます。( B~D も同様 )

          [ 式 12 - 1 ]


          [ fig 12 - 3 ]


[ script 12 - 1 ] は以上の検証のための script です。

Shade 上で選択された自由曲面の指定するブロックの bezier patch を線形状群として出力するもので、[ 式 12 - 1 ] で求めたものと、DXF ファイル経由で得たものの2種類を出力し、比較します。


検証結果
               [ fig 12 - 4 ]


< script 実行の前に >

bezier patch の16個の制御点の内、従属変数とした A, B, C, D 4つの制御点座標を得る機能は script には用意されていませんが、DXF エクスポートで「 自由曲面としてエクスポート 」すると、16個の制御点座標を入手できます。

この script を実行する前に予め DXF エクスポートのプロパティ設定を次の要領でマニュアルで行っておく必要があります。

  • メニュー > ファイル > エクスポート >DXF を選択

  • DXFエクスポート設定欄の「 ベジェ曲面 」にチェックを入れる

  • OK ボタンを押す

  • 保存のためのダイアログが現れるので「 キャンセル 」ボタンを押す

一度このプロパティを設定しておけば、Shade を再起動してもしばらくはその設定が継続されたままになります。

     

     [ fig 12 - 4 ]


[ script 12 - 1 ]

import labo

#  bet_bezier_patch(bP as vec3 matrix)
#  bz 内の4つの未定義 bezier patch 座標をセット
#
def set_bezier_patch(bP) :
	for [[i, j], [ii, jj]] in [[[1, 1],[-1, -1]],[[1, 2], [-1, 1]], [[2, 1], [1, -1]], [[2, 2], [1, 1]]] :
		p0 = bP[i + ii][j + jj]
		p1 = bP[i + ii][j]
		p2 = bP[i ][j + jj]
		bP[i][j] = p1 + p2 - p0
			


scene = xshade.scene()

#  bezier patch を求める自由曲面のブロック指定
m = 0
n = 0

#  選択自由曲面の m, n ブロックの12個の bezier patch 制御点座標を取得
bP = labo.get_bezier_patch_base(xshade, m, n) 	

#  4つの未定義 bezier patch 座標をセット
set_bezier_patch(bP)

#  選択自由曲面の m, n ブロックの bezier patch を DXF 経由で取得
bP2 = labo.get_bezier_patch_by_DXF(xshade, m, n)

scene.create_part()
labo.make_bezier_surface(xshade, bP)		#  当該ブロックを自由曲面として出力
	
#  bezier patch  bP を出力
labo.make_bezier_patch(xshade, bP, 'bezier patch')

#  bezier patch  bP2 を出力
labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

scene.select_parent(1)

16 Bezier Patch 分割 [ Shade Labo ]
#3

12 - 3 Patch 補正


[ script 12 - 1 ] を球体状の自由曲面に適用すると、極の部分で異なった結果になります。

               [ fig 12 - 4 ]


極部分のコントロールポイントには交差方向のハンドルがありませんから [ 式 12 - 1 ] に従えば、上図中央のようになりますし、DXF で与えられる bezier patch の方が滑らかな球面を構成しそうだということも感覚的に予想できます。

Shade ではハンドルが出ていない場合に補正を加えて赤い patch のように修正します。




この補正は次のようになっています。

  • bezier patch のアンカーポイント P において、そこから伸びるハンドル h0 - P に長さがなく( *1 )、かつ、その交差方向のハンドル h1 - P に長さがある場合( *2 )、奥にあるハンドル H - Q の 1/2 長さのベクトルを h1 に加える。

          [ 式 12 - 2 ]

  • ハンドル長さの 0 判定は次による

     *1 の判定では bounding box size x 1/10000 以下
     *2 の判定では bounding box size x 1/1000 以下


なお、bounding box size( X, Y, Z 方向の box size の和 ) は 12個の制御点座標から求められるもので、xshade.scene().active_shape().bounding_box_size で得られるものとは異なります。

          [ fig 12 - 5 ]


よって bezier patch を求める関数は次のようになり、labo module に登録してあります。


[ scripr 12 - 2 ]

#  get_bezier_patch(xshade as xshade, m as int, n as int, uuid as string = None ) as vec3 matrix
#	自由曲面の指定する区間 m, n ( 0基数 ) の bezier patch 座標を vec3 matrix として返す
#	デフォルトでは選択自由曲面、uuid に Shade で取得した自由曲面の uuid を渡せば、そのデータを取得
#
def get_bezier_patch(xshade, m, n, uuid = None) :
	bP = get_bezier_patch_base(xshade, m, n, uuid) 		#  12 個の bezier pach 座標を取得
	if bP == None :
		return None
		
	bb = vec3_bounding_box_size(bP)						#  bP の bounding box size
	epsilon1 = bb[3]/10000								#  0 handle 判定しきい値
	epsilon2 = bb[3]/1000								#  0 handle 判定しきい値
	
	#  4 つの bezier patch 座標をセット
	for [[i, j], [ii, jj]] in [[[1, 1],[-1, -1]],[[1, 2], [-1, 1]], [[2, 1], [1, -1]], [[2, 2], [1, 1]]] :
		p0 = bP[i + ii][j + jj]
		p1 = bP[i + ii][j]
		p2 = bP[i ][j + jj]

		v1 = p1 - p0
		v2 = p2 - p0
		if (v1.abs() <= epsilon1) and (v2.abs() > epsilon2) :
			p1 = p0 + (bP[i - 2*ii][j] - bP[i - 2*ii][j + jj])/2
		if (v2.abs() <= epsilon1) and (v1.abs() > epsilon2) :
			p2 = p0 + (bP[i][j - 2*jj] - bP[i + ii][j - 2*jj])/2
	
		bP[i][j] = p1 + p2 - p0
			
	return bP

[ script 12 - 3 ] ~ [ script 12 - 5 ] でこれを検証します。
[ script 12 - 3 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for h in [4, 4.0001] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio = h/bb[3]					

	scene.create_part(str(ratio))

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	scene.select_parent(1)
	
scene.select_parent(1)

[ script 12 - 4 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for [h1, h2] in [[4, 40], [4, 40.001], [40, 4], [40.001, 4]] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, h2], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h1, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio1 = float(h1)/bb[3]					
	ratio2 = float(h2)/bb[3]
	
	if ratio1 >=1e-3 :
		t1 = str(ratio1*1000) + 'e-3'
	else :
		t1 = str(ratio1*10000) + 'e-4'	
	if ratio2 >=1e-3 :
		t2 = str(ratio2*1000) + 'e-3'
	else :
		t2 = str(ratio2*10000) + 'e-4'	

	scene.create_part(t1 + '_' +  t2)

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	scene.select_parent(1)
	
scene.select_parent(1)

[ script 12 - 5 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for h in [5.321, 5.322] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	scene.create_part()

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	#  自由曲面を回転
	scene.move_object([0, 0, 0], None, [0, -115, 0], None)
	scene.move_object([0, 0, 0], None, [40, 0, 0], None)
	scene.move_object([0, 0, 0], None, [0, 0, 48], None)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bP)
	ratio = h/bb[3]
	scene.select_parent(1)
	scene.active_shape().name = str(ratio)
	
scene.select_parent(1)

#4

EN:
There seems to be more going on with curved surface generation than what is shown in this post, especially since the release of Shade3D 17.1.

In short, I detect noticeable deviations from freeform surfaces obtained by Equation 12-1 and the polygon output from Shade. None of the vertices of the polygon output lie on the surfaces obtained from Equation 12-1, save for the boundary curves. Given a surface with an average bounding box of 950 mm, the surface obtained by Equation 12-1 deviates from the vertices of Shade’s polygon output by as much as 5mm.

JA:
特にShade3D 17.1がリリースされて以来、この記事で示されているものよりも湾曲したサーフェスの生成が多く発生しているようです。

つまり、式12-1で得られた自由曲面からの目立つ偏差とシェードのポリゴン出力を検出します。 ポリゴン出力の頂点のいずれも、式12-1で得られたサーフェス上にはなく、境界曲線のために保存されていません。 平均外接矩形が950mmの表面が与えられると、式12-1によって得られた表面は、シェードのポリゴン出力の頂点から5mmだけずれている。


#5

Hi, thank you for your advice.

I confirmed the problem, and found the below.



Shade pythn script problem ( Shade 16, 17, 18 )

Shade script “xshade.scene().save_DXF()” doesn’t support file DXF export setting properties.

The check of “Bezier curved surface” doesn’t work.

I will revise related scripts.

pic%201



Shade 17 / 18 changed Bezier patch base

< Shade 15 / 16 >
Bezier patches by DXF file and rendering base are different.

< Shade 17 / 18 >
Bezier patches by DXF file and rendering base are the same, but the patches are different between Shade 15/16 and Shade 17/18.


I guess that Shade 17/18 changed DXF file library and rendering patch library due to NURBS support.

It takes time, but I will examine the Bezier Patch and report it.


#6

I also noticed that curved surfaces render different than if they were converted to polygon mesh objects. Converting to a polygon mesh at higher subdivision levels reveals
Both are rendered at subdivision level 4 in Shade 12.

I guess results like these, when converting to a polygon mesh, is what prompted the change to the surface generation algorithm in Shade 17.1?


#7

Ja, I’ve recognized the problem of surface subdivision at rendering.

We have two major problems to be solved.

One is bezier patch interpolate alogorithm of Sade 17 and the other is surface subdivision algorithm at rendering.

The former is already found out.

The algorithm is quite difficult and need illustration to explain.

pic%2016

The latter isn’t just for Shade 17, but for any Shade versions.

I have no idea yet.

Anyway I’m already writing a draft of new Shade Labo topic, please give me some time.