SketchUp的自动化探索 (三)一键生成建筑群

在场景建模中,

除了地形,

还有场地周边复杂的建筑群。
大多数人会复制一些白盒子来丰富周边环境。

但是复制出来的效果在表现上还是有不足之处的,
在大面积的周边地形上手动去拉体块也比较费时间。

那今天我们就来制作一个快速生成 建筑群体 的插件。

本次平面分割的算法由 土豆儿 提供,在这表示感谢!

【插件获取方式见文末】

随机生成建筑群

在这里插入图片描述
在这里插入图片描述

场景赏析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(赏析图片来源于设计e周)

虽然做不到上面这些场景那样丰富多变,

但是我们也可以达到 一键生成 简单的随机体块来满足需求。

思路分析

  • 通过Sketchup::InputPoint.pick获取当前选取的平面;
  • 通过UI::HtmalDialog绘制UI界面接收分割参数;
  • 通过分割算法来分割选取的平面并偏移和推拉。

因为在分割平面的时候需要绘制一个简单的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的奇妙之旅 (三)制作一个完整的插件

在这里插入图片描述

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值