在场景建模中,
除了地形,
那今天我们就来制作一个快速生成 建筑群体 的插件。
本次平面分割的算法由 土豆儿 提供,在这表示感谢!
【插件获取方式见文末】
随机生成建筑群▼
场景赏析▼
(赏析图片来源于设计e周)
虽然做不到上面这些场景那样丰富多变,
但是我们也可以达到 一键生成 简单的随机体块来满足需求。
思路分析
因为在分割平面的时候需要绘制一个简单的UI界面,
来接收一些分割参数,
所以本次教程还重点用到了UI::HtmlDialog,
学习如何在ruby中接收前端传过来的参数。
1、通过鼠标获取选中的面▼
我们在选择水平面的时候做一个交互显示选中的平面:
对应代码:
def draw(view)
view.line_width = 3
color = Sketchup::Color.new(255, 165, 0)
color.alpha = 150
view.drawing_color = color
# 在视图中绘制一个临时的面
view.draw(GL_TRIANGLE_FAN, @positions)
end
我们监控鼠标左键点击onLButtonDown,
获取当前点击的面,
把面的顶点存到@positions里面,
后续通过这个些点来分割平面:
后续通过这个些点来分割平面:
def onLButtonDown(_flags, x, y, view)
@ip.pick(view, x, y)
@pick_pt = @ip.position
# 获取选中的面
@select_face = @ip.face
@vertices = @select_face.vertices
# 把面的顶点存到 @positions 中用于后续分割
@positions = @vertices.map do |i|
i.position.to_a
end
view.invalidate
end
2、绘制UI界面▼
之前一直用UI.inputbox来接受参数,
这次我们自己绘制一个UI界面:
我们先实例化一个UI::HtmlDialog,
dialog.set_file path引用我们的html文件,
即可在Skethup中开启一个页面。
对应代码:
dialog = UI::HtmlDialog.new(
dialog_title: '城市场地建造',
preferences_key: 'com.sample.plugin',
scrollable: false,
resizable: false,
width: 300,
height: 500,
left: 100,
top: 100,
min_width: 300,
min_height: 500,
max_width: 300,
max_height: 500,
style: UI::HtmlDialog::STYLE_DIALOG
)
path = './demo.html'
dialog.set_file path
dialog.show
界面有了,
我们还需要从界面的输入框中 取出数据,
再 传给 ruby去处理。
前端把输入框数据传给ruby:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
/* 省略样式 */
</style>
<script type="text/javascript">
/* 从分割宽度输入框取值 */
function drawFace() {
var min_width = document.getElementById('min_width').value;
window.location = 'skp:drawFace@' + min_width
}
/* 从最大/小高度和偏移输入了取值 */
function pushFace() {
var min_height = document.getElementById('min_height').value;
var max_height = document.getElementById('max_height').value;
var offset = document.getElementById('offset').value;
/* 传个ruby */
window.location = 'skp:pushFace@' + min_height + ';' + max_height + ';' + offset
}
</script>
</head>
<body>
<!-- 省略控件代码 -->
</body>
</html>
ruby 接收 页面传过来的数据:
$dialog.add_action_callback('drawFace') do |_action_context, min_width|
# 分割平面
# 最小矩形宽度
min_width = min_width.to_f
# to do ...
end
$dialog.add_action_callback('pushFace') do |_action_context, params|
# 推拉平面
params = params.split(';')
# 最小推拉高度
min_height = params[0].to_f
# 最大推拉高度
max_height = params[1].to_f
# 偏移百分比
offset = params[2].to_f
# to do ...
end
html等前端知识点感兴趣的小可爱可以去菜鸟教程速成一波。
3、分割偏移推拉▼
分割:
偏移:
推拉:
通过前面两步,
我们得到了选中的 面 和我们的 分割参数 ,
接下来我们就需要对面进行分割,
我们需要通过我们预设的参数来控制形体:
单个体块最小宽度——控制体块大小长宽
体块从分割矩形偏移的百分比——控制体块间距
最大/小推拉高度——控制体块高度
那我们就定义一个 split_face 方法来分割平面,
positions 是我们前面选取的面的顶点坐标集合。
随机分割平面对应代码:
# 分割平面
def split_face(positions, min_width)
# 取面x区间1/3到2/3处的随机点
max_x = positions.map(&:x).max
min_x = positions.map(&:x).min
start_x = min_x + (max_x - min_x) / 3
end_x = min_x + (max_x - min_x) / 3 * 2
random_x = rand(start_x...end_x)
# 取面y区间1/3到2/3处的随机点
max_y = positions.map(&:y).max
min_y = positions.map(&:y).min
start_y = min_y + (max_y - min_y) / 3
end_y = min_y + (max_y - min_y) / 3 * 2
random_y = rand(start_y...end_y)
# 通过随机点和面四个顶点
# 划分成四个矩形[[[x1,y1,z1],[,,],[,,],[,,],[...]]
# 把划分好的矩形顶点存入心的数组
new_positions = positions.map do |position|
[position, [position.x, random_y, 0], [random_x, random_y, 0], [random_x, position.y, 0]]
end
# 遍历划分好的矩形顶点数组
face_positions = []
new_positions.each do |positions|
# 获取划分好的矩形区间
max_x = positions.map(&:x).max
min_x = positions.map(&:x).min
max_y = positions.map(&:y).max
min_y = positions.map(&:y).min
# 判断是否满足最小宽度
if (max_x - min_x) < min_width || (max_y - min_y) < min_width
face_positions << positions
else
# 如果不满足继续分割
face_positions += split_face(positions, min_width)
end
end
# 返回分割好的矩形顶点数组
face_positions
end
再通过分割好的平面,
对矩形进行向内偏移和拉高。
缩进推拉对应代码:
# 缩进和推拉矩形
def push_face(face_positions, push_min, push_max, offset)
# 缩进
zoom_face_positions = face_positions.map do |positions|
# 分割面的对顶点
position0 = positions[0]
position2 = positions[2]
# 计算一个分割面的的x轴最大间距,往里面偏移0.2的间距
max_x = [position0.x, position2.x].max
min_x = [position0.x, position2.x].min
distance_x = (max_x - min_x) * offset / 100.0
max_y = [position0.y, position2.y].max
min_y = [position0.y, position2.y].min
distance_y = (max_y - min_y) * offset / 100.0
# 得到偏移之后的面集合
zoom_position0 = [min_x + distance_x, min_y + distance_y, 0]
zoom_position2 = [max_x - distance_x, max_y - distance_y, 0]
[zoom_position0, [zoom_position0.x, zoom_position2.y, 0], zoom_position2, [zoom_position2.x, zoom_position0.y, 0]]
end
# 随机推拉一定高度
zoom_face_positions.each_with_index do |positions, index|
face = entities.add_face(positions)
face.reverse! unless face.normal.samedirection?(Z_AXIS)
rand_height = rand(push_min..push_max)
face.pushpull(rand_height)
zoom_face_positions[index].z = rand_height
end
end
end
总结
这次只针对了水平面上进行了随机的矩形分割,
配景建筑如果能带一些细节,
那效果会更好。
我们可以结合上一节的建筑表皮插件,
在推拉好的体块四周,
调用我们的 表皮生成 方法,
就能自动 批量 为体块增加表皮细节:
或者在分割的时候随机出 不同的形状 进行推拉,
或者在推拉之后在 顶面 的基础上再进行
推拉/缩放 等操作。
如果这个方向对你有帮助,
欢迎小可爱们继续深入探索。
本次的 city_builder.rbz 是一个带UI界面的插件样例,
公众号回复 参数化城市 获取完整插件。
SU自动化相关文章推荐:
SketchUp的自动化探索 (二)建筑表皮生成器
SketchUp的自动化探索 (一)构建私有模型库
SU相关文章推荐:
SketchUp的奇妙之旅 (四)自动化建筑遐想
SketchUp的奇妙之旅 (三)制作一个完整的插件
用技术探索艺术
Explore art with technology