根据模型文件的抽取信息(动画节点名、模型中心点坐标、模型旋转值、缩放值、模型的长宽高、包围盒的中心点坐标,包含16个属性)可知,只要知道节点名,我们便能获得场景文件中模型的中心点坐标、模型中心点坐标、模型旋转值、缩放值、模型的长宽高等信息,通过这些信息作为输入,用我们设计的投影算法来划分场景空闲空间。
投影算法思想:将所有模型投影到三个坐标轴,被投影区域覆盖的为投影区,没有被覆盖的为空闲区间,将每个坐标轴的空闲空间与其他坐标轴的投影区间相交织,便可得出一个个我们所需的场景空间。
下图为一个简单的场景,其中有两个栅栏和一个雪人,场景中的模型都抽象为立方体包围盒。如下图(c)未被覆盖的区域就是空白区间段,由各坐标轴空闲区间和投影区间交织出来的空间区域就是可以划分出来作为场景空间的区域。
最后根据计算出来的最后结果,在场景中创建一个个把场景空间包围起来的包围盒,使场景实体化,最后由用户来选择自动划分出来的场景空间结果,需要的场景空间保留,不需要的删除。
投影算法实现:
1 收集场景中所有模型节点的中心点坐标以及长宽高信息。
2 计算场景中各模型节点在 X 和 Y 坐标轴的投影区间,用 busyX,busyY,两个数组保存。比如第 i 个节点的中心点X轴坐标为x,节点宽度为 width,则busyX[i].left=x-width/2, busyX[i].right=x+width/2;busyX[i]中的 left 和 right 分别指的是投影区域的最小值和最大值;Y坐标轴的投影区域计算同理。
3. 计算所有模型节点底部 Z 轴坐标(即模型节点 Z 轴坐标减去其高度一半)的最小值bottle和所有模型节点顶部 Z轴坐标(即模型节点 Z轴坐标加上其高度一半)的最大值top。
4. 给 busyX 和 busyY 两个数组按照 left 的值从小到大排序,即按照各投影区间的最小值的大小排序。
5.合并 busyX 和 busyY 中重叠区域,即如果 busyX[i].right>busyX[i+1].left,那么说明投影区间i 和i+1存在重叠,需要合并,合并之后的区间 left 值为busyX[i].left,right 值为 busyX[i+1].right。
6.根据 busyX 和 busyY 计算 idleX 和 idleY 数组,idleX 和 idleY 数组分别为busyX 和 busyY 数组所未包含的区域,即投影区间所未覆盖的空闲区间。
7.以 idleX 中的区间为 width,busyY 中的区间为 depth,两两搭配交织成高度为0的平面场景空间,比如 idleX[i]和busyY[j]交织,形成width 为idleX[i].right-idleX[i].left,depth 为 busyY[j]. right-busyY[j].left,高度为 0 的平面场景空间,场景空间中心点在 X,Y,Z轴上的坐标分别为:(idleX[i].left+ idleX[i].right)/2,(busyY[j].left+ busyY[j].right)/2,bottle。
8.以 busyX 中的区间为 width,idleY 中的区间为 depth,两两搭配交织成高度为0的平面场景空间,比如 busyX[i]和idleY[j]交织,形成width 为busyX[i].right-busyX[i].left,depth 为 idleY[j]. right-idleY[j].left,高度为 0 的平面场景空间,场景空间中心点在 X,Y,Z轴上的坐标分别为:(busyX[i].left+ busyX[i].right)/2,(idleY[j].left+ idleY[j].right)/2,bottle。
9.在所有计算出的场景空间创建和场景空间中心点以及大小都相等的场景空间包围盒,使场景空间实体化,并以“SP_”开头的节点名命名这些包围盒。
伪代码:
// 输入:模型节点列表
// 输出:场景空间包围盒节点列表
function createSceneBoundingBox(models):
// 步骤1:收集模型节点的中心点坐标和长宽高信息
centerPoints = []
dimensions = []
for model in models:
center = model.center // 模型节点的中心点坐标
width = model.width // 模型节点的宽度
height = model.height // 模型节点的高度
depth = model.depth // 模型节点的深度
centerPoints.append(center)
dimensions.append((width, height, depth))
// 步骤2:计算模型节点在X和Y轴的投影区间
busyX = []
busyY = []
for i in range(len(models)):
center = centerPoints[i]
width = dimensions[i][0]
leftX = center.x - width/2
rightX = center.x + width/2
busyX.append((leftX, rightX))
// 计算Y轴投影区间同理
// 步骤3:计算模型节点底部Z轴坐标的最小值和顶部Z轴坐标的最大值
bottle = min(center.z - height/2 for center, height in zip(centerPoints, dimensions))
top = max(center.z + height/2 for center, height in zip(centerPoints, dimensions))
// 步骤4:按照left值从小到大对busyX和busyY进行排序
busyX.sort(key=lambda x: x[0])
busyY.sort(key=lambda y: y[0])
// 步骤5:合并重叠的投影区间
mergedX = mergeInterval(busyX)
mergedY = mergeInterval(busyY)
// 步骤6:计算空闲区间
idleX = computeIdleIntervals(mergedX)
idleY = computeIdleIntervals(mergedY)
// 步骤7:生成平面场景空间
sceneSpaces = []
for intervalX in idleX:
for intervalY in mergedY:
centerX = (intervalX[0]+intervalX[1])/2
centerY = (intervalY[0]+intervalY[1])/2
sceneSpace = createSceneSpace(centerX, centerY, bottle)
sceneSpaces.append(sceneSpace)
// 步骤8:生成平面场景空间
for intervalX in mergedX:
for intervalY in idleY:
centerX = (intervalX[0]+intervalX[1])/2
centerY = (intervalY[0]+intervalY[1])/2
sceneSpace = createSceneSpace(centerX, centerY, bottle)
sceneSpaces.append(sceneSpace)
// 步骤9:创建场景空间包围盒节点
boundingBoxes = []
for sceneSpace in sceneSpaces:
boundingBox = createBoundingBox(sceneSpace.center, sceneSpace.width, sceneSpace.depth, sceneSpace.height)
boundingBox.name = "SP_" + generateUniqueID() // 以"SP_"为前缀命名包围盒节点
boundingBoxes.append(boundingBox)
return boundingBoxes
// 合并重叠的区间
function mergeInterval(intervals):
merged = []
for interval in intervals:
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
else:
merged[-1] = (merged[-1][0], max(merged[-1][1], interval[1]))
return merged
// 计算空闲区间
function computeIdleIntervals(mergedIntervals):
idleIntervals = []
prevRight = 0
for interval in mergedIntervals:
left = prevRight
right = interval[0]
if left < right:
idleIntervals.append((left, right))
prevRight = interval[1]
return idleIntervals
// 创建场景空间
function createSceneSpace(centerX, centerY, bottle):
// 这里使用自定义的SceneSpace类表示场景空间
// SceneSpace类包含中心点坐标(centerX, centerY, bottle)和宽度、深度、高度的信息
return SceneSpace(centerX, centerY, bottle, width, depth, 0)
// 创建包围盒节点
function createBoundingBox(center, width, depth, height):
// 这里使用自定义的BoundingBox类表示包围盒节点
// BoundingBox类包含中心点坐标center和宽度、深度、高度的信息
return BoundingBox(center, width, depth, height)
// 生成唯一的ID
function generateUniqueID():
// 实现一个生成唯一ID的逻辑,例如使用时间戳、随机数等方法生成唯一ID
// 省略实现细节,这里假设已经能够生成唯一ID
return uniqueID
// 定义SceneSpace类
class SceneSpace:
constructor(centerX, centerY, bottle, width, depth, height):
this.center = (centerX, centerY, bottle)
this.width = width
this.depth = depth
this.height = height
// 定义BoundingBox类
class BoundingBox:
constructor(center, width, depth, height):
this.center = center
this.width = width
this.depth = depth
this.height = height
java实现:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SceneBoundingBox {
public static List<BoundingBox> createSceneBoundingBox(List<Model> models) {
List<Point3D> centerPoints = new ArrayList<>();
List<Dimension3D> dimensions = new ArrayList<>();
// 步骤1:收集模型节点的中心点坐标和长宽高信息
for (Model model : models) {
Point3D center = model.getCenter();
double width = model.getWidth();
double height = model.getHeight();
double depth = model.getDepth();
centerPoints.add(center);
dimensions.add(new Dimension3D(width, height, depth));
}
// 步骤2:计算模型节点在X和Y轴的投影区间
List<Interval> busyX = new ArrayList<>();
List<Interval> busyY = new ArrayList<>();
for (int i = 0; i < models.size(); i++) {
Point3D center = centerPoints.get(i);
double width = dimensions.get(i).getWidth();
double leftX = center.getX() - width / 2;
double rightX = center.getX() + width / 2;
busyX.add(new Interval(leftX, rightX));
// 计算Y轴投影区间同理
}
// 步骤3:计算模型节点底部Z轴坐标的最小值和顶部Z轴坐标的最大值
double bottle = Collections.min(getBottomZCoordinates(centerPoints, dimensions));
double top = Collections.max(getTopZCoordinates(centerPoints, dimensions));
// 步骤4:按照left值从小到大对busyX和busyY进行排序
Collections.sort(busyX);
Collections.sort(busyY);
// 步骤5:合并重叠的投影区间
List<Interval> mergedX = mergeIntervals(busyX);
List<Interval> mergedY = mergeIntervals(busyY);
// 步骤6:计算空闲区间
List<Interval> idleX = computeIdleIntervals(mergedX);
List<Interval> idleY = computeIdleIntervals(mergedY);
// 步骤7:生成平面场景空间
List<SceneSpace> sceneSpaces = new ArrayList<>();
for (Interval intervalX : idleX) {
for (Interval intervalY : mergedY) {
double centerX = (intervalX.getLeft() + intervalX.getRight()) / 2;
double centerY = (intervalY.getLeft() + intervalY.getRight()) / 2;
SceneSpace sceneSpace = createSceneSpace(centerX, centerY, bottle);
sceneSpaces.add(sceneSpace);
}
}
for (Interval intervalX : mergedX) {
for (Interval intervalY : idleY) {
double centerX = (intervalX.getLeft() + intervalX.getRight()) / 2;
double centerY = (intervalY.getLeft() + intervalY.getRight()) / 2;
SceneSpace sceneSpace = createSceneSpace(centerX, centerY, bottle);
sceneSpaces.add(sceneSpace);
}
}
// 步骤8:创建场景空间包围盒节点
List<BoundingBox> boundingBoxes = new ArrayList<>();
for (SceneSpace sceneSpace : sceneSpaces) {
BoundingBox boundingBox = createBoundingBox(sceneSpace.getCenter(), sceneSpace.getWidth(),
sceneSpace.getDepth(), sceneSpace.getHeight());
boundingBox.setName("SP_" + generateUniqueID()); // 以"SP_"为前缀命名包围盒节点
boundingBoxes.add(boundingBox);
}
return boundingBoxes;
}
private static List<Double> getBottomZCoordinates(List<Point3D> centerPoints, List<Dimension3D> dimensions) {
List<Double> bottomZCoordinates = new ArrayList<>();
for (int i = 0; i < centerPoints.size(); i++) {
Point3D center = centerPoints.get(i);
double height = dimensions.get(i).getHeight();
double bottomZ = center.getZ() - height / 2;
bottomZCoordinates.add(bottomZ);
}
return bottomZCoordinates;
}
private static List<Double> getTopZCoordinates(List<Point3D> centerPoints, List<Dimension3D> dimensions) {
List<Double> topZCoordinates = new ArrayList<>();
for (int i = 0; i < centerPoints.size(); i++) {
Point3D center = centerPoints.get(i);
double height = dimensions.get(i).getHeight();
double topZ = center.getZ() + height / 2;
topZCoordinates.add(topZ);
}
return topZCoordinates;
}
private static List<Interval> mergeIntervals(List<Interval> intervals) {
List<Interval> merged = new ArrayList<>();
for (Interval interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1).getRight() < interval.getLeft()) {
merged.add(interval);
} else {
Interval last = merged.get(merged.size() - 1);
last.setRight(Math.max(last.getRight(), interval.getRight()));
}
}
return merged;
}
private static List<Interval> computeIdleIntervals(List<Interval> mergedIntervals) {
List<Interval> idleIntervals = new ArrayList<>();
double prevRight = 0;
for (Interval interval : mergedIntervals) {
double left = prevRight;
double right = interval.getLeft();
if (left < right) {
idleIntervals.add(new Interval(left, right));
}
prevRight = interval.getRight();
}
return idleIntervals;
}
private static SceneSpace createSceneSpace(double centerX, double centerY, double bottomZ) {
double width = 0; // Specify the width value according to your requirements
double depth = 0; // Specify the depth value according to your requirements
double height = 0; // Specify the height value according to your requirements
return new SceneSpace(centerX, centerY, bottomZ, width, depth, height);
}
private static BoundingBox createBoundingBox(Point3D center, double width, double depth, double height) {
return new BoundingBox(center, width, depth, height);
}
private static String generateUniqueID() {
// Implement your logic to generate a unique ID here
// For simplicity, let's assume it returns a random ID string
return UUID.randomUUID().toString();
}
}
class Point3D {
private double x;
private double y;
private double z;
public Point3D(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
}
class Dimension3D {
private double width;
private double height;
private double depth;
public Dimension3D(double width, double height, double depth) {
this.width = width;
this.height = height;
this.depth = depth;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public double getDepth() {
return depth;
}
}
class Interval implements Comparable<Interval> {
private double left;
private double right;
public Interval(double left, double right) {
this.left = left;
this.right = right;
}
public double getLeft() {
return left;
}
public double getRight() {
return right;
}
public void setRight(double right) {
this.right = right;
}
@Override
public int compareTo(Interval interval) {
return Double.compare(this.left, interval.left);
}
}
class SceneSpace {
private Point3D center;
private double width;
private double depth;
private double height;
public SceneSpace(double centerX, double centerY, double bottomZ, double width, double depth, double height) {
this.center = new Point3D(centerX, centerY, bottomZ + height / 2);
this.width = width;
this.depth = depth;
this.height = height;
}
public Point3D getCenter() {
return center;
}
public double getWidth() {
return width;
}
public double getDepth() {
return depth;
}
public double getHeight() {
return height;
}
}
class BoundingBox {
private Point3D center;
private double width;
private double depth;
private double height;
private String name;
public BoundingBox(Point3D center, double width, double depth, double height) {
this.center = center;
this.width = width;
this.depth = depth;
this.height = height;
}
public Point3D getCenter() {
return center;
}
public double getWidth() {
return width;
}
public double getDepth() {
return depth;
}
public double getHeight() {
return height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Model {
private Point3D center;
private double width;
private double height;
private double depth;
public Model(Point3D center, double width, double height, double depth) {
this.center = center;
this.width = width;
this.height = height;
this.depth = depth;
}
public Point3D getCenter() {
return center;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public double getDepth() {
return depth;
}
}
需要根据自己的需求,使用具体的绘制引擎或框架进行适当补充。
参考文献:张杨.手机3D动画自动生成系统中动画场景语义标注系统的设计与实现.[北京工业大学硕士学位论文].2013.