Friday, 28 February 2020

Week 8 - Material ID Experimentation

I spent a couple of days trying to figure out how to solve the problem with the material IDs, since Houdini doesn't keep the Material IDs in a way, that Unreal can use them (This might be a problem specific to our workflow, 'obj' files get imported with Materials instead of Principled Shaders).

1 - Automating the process
Since we are currently working with about 50 Modular Kit pieces and 20 Material IDs it would be very tedious to manually set up the Materials. So I did a bit of testing.
All the pieces come with an attribute called 'shop_materialpath', which I can use to get the faces with different IDs.

Material ID List
Unfortunately I could not find out how to add material inputs for the HDA, or if that is even possible, so for now we use String Parameters.
I also couldn't figure out how to edit the parameters via Python, since the geometry of the nodes is read only and the python node doesn't own the geometry, because it is not connected to the geometry nodes.
However I can use Python to create nodes, so I created an Attribute Wrangle node in each Modular Kit Node, containing the necessary logic to assign the 'unreal_material' attribute + values.

node = hou.pwd()
# Add code to modify contained geometries.
# Use drop down menu to select examples.
subnetNode = node.parent()
children = subnetNode.children()
#assign unreal material attribute via VEX
attribWString = """int numPrims = @numprim;
for(int p = 0; p < numPrims; p++){
string mat = primattrib(geoself(), 'shop_materialpath', p, 1);
mat = tolower(mat);
mat = re_replace(' ', '_', mat);
mat = 'mat_' + mat;
setprimattrib(geoself(), 'unreal_material', p, chs('../../' + mat), 'set');
}"""
for c in children:
if c.type().name() and 'SM_' in c.name():
name = c.name()
n = hou.node('../' + name + '/' + name)
if str(n) != 'None':
n.setHardLocked(True)
a = hou.node('../' + name + '/unreal_material')
if str(a) == 'None':
a = c.createNode('attribwrangle')
a.setName('unreal_material')
a.setFirstInput(n)
a.moveToGoodPosition()
a.parm('snippet').set(attribWString)
a.setDisplayFlag(True)
a.setRenderFlag(True)
else:
a.moveToGoodPosition()
a.parm('snippet').set(attribWString)
a.setDisplayFlag(True)
a.setRenderFlag(True)
view raw material_ID.py hosted with ❤ by GitHub
I added Parameters for each Material ID, which get referenced by the attribute. This time I added the Parameters manually, but I could have automated this in a similar way with how I did the .json file evaluation. This would make it more flexible for using different kits, however due to time constraints I chose to do it manually for now.

I also locked the geometry files, so the HDA has no file dependencies.

2 - Unreal
So, to keep it short: It works.

Material IDs in Unreal




Now that I got this working I will move on to LODs and hopefully soon more Shader and Engine work.

Wednesday, 26 February 2020

Week 6.2 and 7 - Finally Buildings

After about two not as productive weeks I finally completed the building construction. I had to go back and redo some bits, because I found out, that Unreal can't cook the Python Module nodes.

Looks like a building to me

1 - Roofs redone
Since I initially used Python to create the roof meshes I had to go back and redo the setup, but that turned out to be not that complicated. Instead of using a curve and having to pass the point positions into the coordinate parameter, I used VEX to create a polygon and add the points to it.


float temp[] = detail('op:../../Instance/', 'gfloor_curve_points', 0);
int numPt = len(temp)/3;
int prim = addprim(geoself(), 'poly');
for (int i = 0; i < numPt; i++){
int startIndex = i * 3;
vector pos = set(temp[startIndex], temp[startIndex + 1], temp[startIndex + 2]);
int pt = addpoint(geoself(), pos);
int vert = addvertex(geoself(), prim, pt);
}
I used the following node setup to create the roof meshes and moved each into a separate obj node, so I could use them separately, e.g if I don't need one of them.
EDIT: I added an attribute wrangle node to add a material attribute.


2 - Ground Floor Complete
Since the ground Floor was done in a similar way to the mid floors I won't go into much detail about the process. I cleaned up the code for placing the wall tiles slightly and added a new section for adding unique tiles. I limited the number of unique tiles to 1 per building side, just to simplify things, but in theory it would be possible. I even tried it, but it got a bit buggy.
Here is the code for the unique tiles, nothing special.


//VARIABLES
int addFSep = chi('../../chk_gfloor_sep');
//Points
int points[] = primpoints(geoself(), 0);
int numPt = @numpt;
int cornerPoints[] = points;
int wallPoints[];
int wallPointIndex[];
int basePoints[] = points;
int sepPoints[] = basePoints;
for(int i = 0; i < len(points); i++) {
sepPoints[i] += len(points);
}
append(points, points[0]);
int sortedIndices[] = points;
int numCorners = len(cornerPoints);
int usedBSides[];
//unique pieces
string uniquePieces = chs('../kit_storage/gfloor_kit_unique');
string uniquePieceArray[] = split(uniquePieces);
int numUnique = chi('../../mp_unique_pieces');
float wallHeight = getbbox_size(chs('../../' + uniquePieceArray[0] + '/' + uniquePieceArray[0] + '/file'))[1];;
//separation pieces
string fSep = chs('../kit_storage/fsep_kit_wall');
string sepArray[] = split(fSep);
float sepWidth = getbbox_size(chs('../../' + sepArray[0] + '/' + sepArray[0] + '/file'))[0];
//piece x size
float xWidth[];
vector norm[];
vector pPos[];
//add points
for (int i = 0; i < numUnique; i++){
//Piece Info
int index = chi('../../gfloor_unique_piece_index' + itoa(i+1)) % len(uniquePieceArray);
int side = chi('../../gfloor_unique_building_side' + itoa(i+1));
float offset = chf('../../gfloor_unique_offset' + itoa(i+1));
string pieceName = uniquePieceArray[index];
float pieceWidth = getbbox_size(chs('../../' + pieceName + '/' + pieceName + '/file'))[0];
int startIndex = side;
if(find(usedBSides, side) < 0) {
//Edge Info
int startPoint = sortedIndices[startIndex];
int endPoint = sortedIndices[startIndex + 1];
vector startPos = pointattrib(geoself(), 'P', startPoint, 1);
vector endPos = pointattrib(geoself(), 'P', endPoint, 1);
vector vec = endPos - startPos;
float dist = length(vec) - ( pointattrib(geoself(), 'piece_x_size', startPoint, 1) + pointattrib(geoself(), 'piece_x_size', endPoint, 1));
vector normal = normalize(set(vec[2], vec[1], vec[0] * -1));
if (dist > pieceWidth) {
startPos += normalize(vec) * (pointattrib(geoself(), 'piece_x_size', startPoint, 1) + pieceWidth/2);
dist -= pieceWidth;
vector pos = startPos + offset * dist * normalize(vec);
int pt = addpoint(geoself(), pos);
setpointattrib(geoself(), 'piece_x_size', pt, pieceWidth/2,'set');
setpointattrib(geoself(), 'scale', pt, set(1,1,1),'set');
setpointattrib(geoself(), 'N', pt, normal,'set');
setpointattrib(geoself(), 'instance', pt, '../../'+pieceName,'set');
setpointattrib(geoself(), "row_type", pt, 'wall', "set");
insert(basePoints, startIndex + 1, pt);
append(usedBSides, side);
append(wallPoints, pt);
append(xWidth, pieceWidth);
append(pPos, pos);
append(norm, normal);
append(wallPointIndex, startIndex + 1);
}
}
else{
printf('Side already used. \n');
}
}
//add separation
if(addFSep == 1){
for (int i = 0; i < len(wallPoints); i++) {
float x = xWidth[i];
float scaleX = x / sepWidth;
int index = wallPointIndex[i];
int pt = addpoint(geoself(), pPos[i] + set(0, wallHeight, 0));
setpointattrib(geoself(), 'piece_x_size', pt, xWidth[i],'set');
setpointattrib(geoself(), 'scale', pt, set(scaleX,1,1),'set');
setpointattrib(geoself(), 'N', pt, norm[i],'set');
setpointattrib(geoself(), 'instance', pt, '../../'+sepArray[0],'set');
setpointattrib(geoself(), "row_type", pt, 'sep', "set");
insert(sepPoints, index, pt);
}
}
setdetailattrib(geoself(), 'base_Points', basePoints, 'set');
setdetailattrib(geoself(), 'sep_Points', sepPoints, 'set');
view raw Unique_Tiles.v hosted with ❤ by GitHub

This could and should probably be optimised.
Just for completeness sake I'll add the code for placing the wall tiles, but it is not any different from the middle floor section.

//VARIABLES
int basePoints[] = detail(geoself(), 'base_Points');
append(basePoints, basePoints[0]);
int points[] = primpoints(geoself(), 0);
int numPt = len(points);
int numRows = 2 - (1 - chi('../../chk_gfloor_sep'));
//modular kit piece arrays
//pillars
string pillars = chs('../kit_storage/gfloor_kit_pillar');
string pillarArray[] = split(pillars);
int pillarIndex = int(chi('../../gfloor_pillar_index')%len(pillarArray));
string pillarRef = '../../'+ pillarArray[pillarIndex];
int numPillars = chi('../../gfloor_num_pillars');
float pillarWidth = float(getbbox_size(chs('../../' + pillarArray[0] + '/' + pillarArray[0] + '/file' ))[0]);
//plain wall
string wall = chs('../kit_storage/gfloor_kit_wall');
string wallArray[] = split(wall);
int wallIndex = chi('../../gfloor_wall_index') % len(wallArray);
float wallWidth = getbbox_size(chs('../../' + wallArray[wallIndex] + '/' + wallArray[wallIndex] +'/file'))[0];
float wallHeight = getbbox_size(chs('../../' + wallArray[wallIndex] + '/' + wallArray[wallIndex] +'/file'))[1];
string wallRef = '../../' + wallArray[wallIndex];
int numWallPieces = chi('../../gfloor_num_pieces_btwn_pillars');
//windows
string windows = chs('../kit_storage/gfloor_kit_sw');
string windowArray[] = split(windows);
int windowIndex = chi('../../gfloor_wframe_index') % len(windowArray);
string pieceRef = '../../' + windowArray[windowIndex];
float pieceWidth = getbbox_size(chs('../../' + windowArray[windowIndex] + '/' + windowArray[windowIndex] +'/file'))[0];
// floor separation
string fSep = chs('../kit_storage/fsep_kit_wall');
string fSepPillar = chs('../kit_storage/fsep_kit_pillar');
string fSepRef = '../../' + rstrip(fSep);
string fSepPillarRef = '../../' + rstrip(fSepPillar);
//PLACE PIECES
for (int row = 0; row < numRows; row++){
float offset = row * wallHeight;
//loop through corners
for (int c = 0; c < len(basePoints) - 1; c++) {
int startPoint = basePoints[c];
int endPoint = basePoints[c + 1];
//PREP
vector startPointPos = pointattrib(geoself(), 'P', startPoint, 1);
vector endPointPos = pointattrib(geoself(), 'P',endPoint, 1);
vector dir = normalize(endPointPos - startPointPos);
// start and end position, taking corner piece width into consideration
vector startPos = startPointPos + (pointattrib(geoself(), 'piece_x_size', startPoint, 1)*dir);
vector endPos = endPointPos - (pointattrib(geoself(), 'piece_x_size', endPoint, 1)*dir);
//normal for placing the pieces on the edges
vector n = set(dir[2], 0, dir[0] * -1);
float dist = distance(startPos, endPos);
if ( (pointattrib(geoself(), 'piece_x_size', startPoint, 1) + pointattrib(geoself(), 'piece_x_size', endPoint, 1)) < distance(startPointPos, endPointPos) ) {
//CALCULATE SECTIONS
if (numWallPieces == 0){
numPillars = 1;
}
float sectionWidth = (numWallPieces * wallWidth) * clamp(numPillars - 1, 0, 10) + numPillars * pillarWidth;
sectionWidth *= int(sectionWidth < dist);
int numSections = int((sectionWidth != 0));
//num tiles on each side of pillar section
float restWidth = dist - sectionWidth;
int numPieces;
if (sectionWidth != 0) {
numPieces = int(floor((restWidth/2)/wallWidth));
numPieces -= (numPieces % 2) * (1 - int(sectionWidth < dist));
}
else {
numPieces = int(floor(restWidth / wallWidth));
}
//filler
float fillerWidth;
if (sectionWidth != 0) {
fillerWidth = (restWidth - ((numPieces * 2) * wallWidth))/2;
}
else {
fillerWidth = (restWidth - ((numPieces) * wallWidth))/2;
}
float fillerScaleX = fillerWidth / wallWidth;
//PLACE PIECES
vector tempPos;
vector tempStartPos;
vector tempEndPos;
vector pillarStartPos;
int tempPt;
//filler
if(numSections != 0 || numPieces != 0){
//set ref
string fillerRef = '';
if(row == 0){
fillerRef = wallRef;
}
else if(row == 1){
fillerRef = fSepRef;
fillerScaleX *= 2;
}
if (fillerWidth != 0) {
tempPos = startPos + (fillerWidth/2) * dir + set(0, offset, 0);
tempStartPos = tempPos;
tempPt = addpoint(geoself(), tempStartPos);
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
tempPos = endPos - (fillerWidth/2) * dir + set(0, offset, 0);
tempEndPos = tempPos;
tempPt = addpoint(geoself(), tempEndPos);
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
else {
//set ref
string fillerRef = '';
if(row == 0){
fillerRef = wallRef;
}
else if(row == 1){
fillerRef = fSepRef;
fillerScaleX *= 2;
}
if (fillerWidth != 0) {
tempPos = startPos + (fillerWidth * dir) + set(0, offset, 0);
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX * 2,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
// add additional pieces
if (numPieces != 0){
string spareRef = '';
float spareScaleX = 1;
if (row == 0){
spareRef = pieceRef;
}
else if (row == 1){
spareRef = fSepRef;
spareScaleX *= 2;
}
vector start, end;
if (sectionWidth != 0) {
for (int p = 0; p < numPieces; p++){
tempPos = tempStartPos + (pieceWidth * dir)/2 + (fillerWidth * dir)/2 + (pieceWidth * p) * dir;
tempPt = addpoint(geoself(), tempPos);
start = tempPos;
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(spareScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
tempPos = tempEndPos - (pieceWidth * dir)/2 - (fillerWidth * dir)/2 - (pieceWidth * p) * dir;
tempPt = addpoint(geoself(), tempPos);
end = tempPos;
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(spareScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
tempStartPos = start;
tempEndPos = end;
pillarStartPos = tempStartPos + pieceWidth/2 * dir;
}
else{
for (int p = 0; p < numPieces; p++) {
tempPos = tempStartPos + (pieceWidth * dir)/2 + (fillerWidth * dir)/2 + (pieceWidth * p) * dir;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(spareScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
}
else{
pillarStartPos = tempStartPos + fillerWidth/2 * dir;
}
//add pillar sections
if(numSections != 0){
string wRef, pRef = '';
float xScale = 1;
if (row == 0){
wRef = pieceRef;
pRef = pillarRef;
xScale = 1;
}
else if (row == 1){
wRef = fSepRef;
pRef = fSepPillarRef;
xScale = 1;
}
for(int pl = 0; pl < numPillars; pl++){
vector pillarPos;
xScale = 1;
pillarPos = pillarStartPos + (pillarWidth/2 * dir) + (pillarWidth * pl * dir) + (numWallPieces * pieceWidth * dir * pl);
tempPt = addpoint(geoself(), pillarPos);
setpointattrib(geoself(), 'instance', tempPt, pRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(xScale,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
for (int sWall = 0; sWall < numWallPieces; sWall++) {
if(pl < numPillars - 1){
if (row == 1) {
xScale = 2;
}
vector wallPos = pillarPos + (pillarWidth/2 * dir) + (wallWidth/2 * dir) + (wallWidth * sWall * dir);
tempPt = addpoint(geoself(), wallPos);
setpointattrib(geoself(), 'instance', tempPt, wRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(xScale,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
}
}
}
}
}
And finally the full ground floor section in action:


This is basically how far I got over the last couple of days. From here on I will focus on sorting the material IDs first and then move on to removing wall sections and creating LODs.
But again: It's a building !!!

Building Variation
And it even works in Unreal, that's progress.

Building Generator in UE4

Wednesday, 12 February 2020

Week 5 and 6.1 - Sorting the Building Middle Floor Section

After thinking I had sorted the .json files  I found out the hard way this week, that once I converted my nodes into an HDA, my python code stopped working, so I guess this will go back onto my To-Do list.
On the bright sight I made quite a bit of progress with the building construction itself and got the middle floor section into a working state.

1 - Corners

First I went back into the Corner Node, cleaned up the code a bit and got rid of a bug, where depending on where point 0 was, it would not get assigned an instance attribute, because some of the angles where messed up. I fixed this by appending point 0 to the point array and working with that instead of always adding a special case for the last point of the curve.


#include <math.h>
//VARIABLES
int points[] = primpoints(geoself(), 0);
int numCorners = len(points);
append(points, 0);
vector pointPos[];
vector edgeDir[];
float cornerAngles[];
//get available corner pieces
string corner_90_param = chs("../kit_storage/mfloor_kit_corner_90");
string corner_90_pieces[] = split(corner_90_param);
string corner_45_param = chs("../kit_storage/mfloor_kit_corner_45");
string corner_45_pieces[] = split(corner_45_param);
//set corner pieces
int cornerIndex = (chi('../../mfloor_corner_index')) % len(corner_90_pieces);
string default_corner_45 = corner_45_pieces[cornerIndex];
string default_corner_90 = corner_90_pieces[cornerIndex];
float wallHeight = getbbox_size(chs("../../" + default_corner_90 + "/" + default_corner_90 + "/file"))[1];
//get point positions
for(int i = 0; i < len(points); i++) {
vector newPos = pointattrib(geoself(), "P", points[i], 1);
pointPos[i] = newPos;
setpointattrib(geoself(), "scale", i, set(1,1,1), "set");
//printf('%g: %e \n', points[i], pointPos[i]);
}
//Direction Vectors between Points
for(int i = 0; i < len(points)-1; i++) {
vector dir = normalize(pointPos[i+1]-pointPos[i]);
edgeDir[i] = dir;
//printf('%g - %g, Vector: %e \n', points[i], points[i+1], edgeDir[i]);
}
append(edgeDir, edgeDir[0]);
//get angles
for(int i = 0; i < len(edgeDir) - 1; i++){
int ptIndex = (i+1) % numCorners;
vector v1 = set(edgeDir[i][0], 0, edgeDir[i][2]);
vector v2 = set(edgeDir[i + 1][0], 0, edgeDir[i + 1][2]);
float angle = atan2(v2[2], v2[0])-atan2(v1[2], v1[0]);
if(angle > PI) {
angle -= 2 * PI;
}
else if(angle <= PI * -1) {
angle += 2 * PI;
}
angle = degrees(angle);
vector n;
//90 degree corners
if(abs(rint(angle)) >= 89 && abs(rint(angle)) <= 91){
n = v2 * -1;
if(angle < 0) {
float theta = radians(-90);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
setpointattrib(geoself(), "N", ptIndex, n, "set");
setattrib(geoself(), "point", "instance", ptIndex, 0, "../../" + default_corner_90, "set");
vector dim_max = getbbox_max(chs("../../" + default_corner_90 + "/" + default_corner_90 + "/file"));
float x_max = dim_max[0];
setattrib(geoself(), "point", "piece_x_size", ptIndex, 0, x_max, "set");
cornerAngles[ptIndex] = 90;
}
//45 degree corners
else if(abs(rint(angle)) >= 44 && abs(rint(angle)) <= 46){
n = v2 * -1;
if(angle < 0) {
float theta = radians(-90);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
else {
float theta = radians(45);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
setpointattrib(geoself(), "N", ptIndex, n, "set");
setattrib(geoself(), "point", "instance", ptIndex, 0, "../../" + default_corner_45, "set");
vector dim_max = getbbox_max(chs("../../" + default_corner_45 + "/" + default_corner_45 + "/file"));
float x_max = dim_max[0];
setattrib(geoself(), "point", "piece_x_size", ptIndex, 0, x_max, "set");
cornerAngles[ptIndex] = 45;
}
printf("%g: %f \n",(i+1) % numCorners, cornerAngles[ptIndex]);
}
This is of course only working for one floor, so I adapted it later, when working on the floor separation. Also I added a parameter for selecting the corner pieces.


2 - Wall Sections

Going back to the wall sections I basically got rid of all my previous code for them. As I stated in a previous blog post, we didn't want to have any scaled tiles, except plain wall tiles, where scaling doesn't matter.
However, since Litha added pillars to the modular kit and wanted to have parameters for 'Number of pillars' per building side and 'Wall Tiles in between Pillars' I had to change the way of how I calculated the tile positioning.
I ended up breaking this down into the following steps:

  1. Calculate the total length of the pillar + in between tiles section and check if it fits onto the building side
  2. Calculate the number of tiles that fit on each side of the pillar section (needs to be an even number)
  3. Fill the rest of the wall length with scaled plain wall tiles.
We agreed to have the pillar section in the centre of the building side for now. Of course it would be possible to add the option of offsetting the section, but  we'll have to see, if it is necessary.

The code bit for this looks like this, again this version only working for one floor, but I will show the multiple floor version later:


//VARIABLES
int points[] = primpoints(geoself(), 0);
append(points, points[0]);
int numPt = len(points);
//modular kit piece arrays
//pillars
string pillars = chs('../kit_storage/mfloor_kit_pillar');
string pillarArray[] = split(pillars);
int pillarIndex = int(chi('../../mfloor_pillar_index')%len(pillarArray));
string pillarRef = '../../'+ pillarArray[pillarIndex];
int numPillars = chi('../../num_pillars');
float pillarWidth = getbbox_size(chs('../../' + pillarArray[0] + '/' + pillarArray[0] + '/file' ))[0];
//plain wall
string wall = chs('../kit_storage/mfloor_kit_wall');
string wallRef = '../../'+ wall;
//wall pieces
string wallPieces = chs('../kit_storage/mfloor_kit_ww');
string wallPieceArray[] = split(wallPieces);
float wallWidth = getbbox_size(chs('../../' + wall + '/' + wall + '/file' ))[0];
int numWallPieces = chi('../../num_pieces_btwn_pillars');
//windows
string windows = chs('../kit_storage/mfloor_kit_window');
string windowArray[] = split(windows);
string windowsRd[], windowsSq[];
//sort windows
for(int w = 0; w < len(windowArray); w++){
if ( match('*rd*', tolower(windowArray[w])) == 1){
append(windowsRd, windowArray[w]);
}
else if ( match('*sq*', tolower(windowArray[w])) == 1){
append(windowsSq, windowArray[w]);
}
}
//Function for adding windows to the Window Frames
void addWindows (string wRd[]; string wSq[]; string wPiece; vector wNormal; vector wPos) {
int wIndex = chi('../../mfloor_window_index') % len(wRd);
int wPt;
string wRef;
if (match('*rd*', tolower(wPiece))) {
wRef = '../../' + wRd[wIndex];
wPt = addpoint(geoself(), wPos);
setpointattrib(geoself(), 'instance', wPt, wRef, 'set');
setpointattrib(geoself(), 'scale', wPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', wPt, wNormal, 'set');
}
else if (match('*sq*', tolower(wPiece))){
wRef = '../../' + wSq[wIndex];
wPt = addpoint(geoself(), wPos);
setpointattrib(geoself(), 'instance', wPt, wRef, 'set');
setpointattrib(geoself(), 'scale', wPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', wPt, wNormal, 'set');
}
}
//PLACE WALL SECTIONS
for(int i = 0; i < numPt - 1; i++){
//PREP
// start and end point position
vector startPointPos = pointattrib(geoself(), 'P', points[i], 1);
vector endPointPos = pointattrib(geoself(), 'P', points[i + 1], 1);
vector dir = normalize(endPointPos - startPointPos);
// start and end position, taking corner piece width into consideration
vector startPos = startPointPos + (pointattrib(geoself(), 'piece_x_size', points[i], 1)*dir);
vector endPos = endPointPos - (pointattrib(geoself(), 'piece_x_size', points[i + 1], 1)*dir);
//normal for placing the pieces on the edges
vector n = set(dir[2], 0, dir[0] * -1);
float dist = distance(startPos, endPos);
//only place tiles, if corner pieces don't overlap
if ( (pointattrib(geoself(), 'piece_x_size', points[i], 1) + pointattrib(geoself(), 'piece_x_size', points[i + 1], 1)) < distance(startPointPos, endPointPos) ) {
//CALCULATE SECTIONS
float sectionWidth = (numWallPieces * wallWidth) * clamp(numPillars - 1, 0, 10) + numPillars * pillarWidth;
sectionWidth *= int(sectionWidth < dist);
int numSections;
numSections = int((sectionWidth != 0));
numWallPieces *= int(numPillars > 1);
//num tiles on each side of pillar section
float restWidth = dist - sectionWidth;
int numPieces;
if (sectionWidth != 0) {
numPieces = int(floor((restWidth/2)/wallWidth));
numPieces -= (numPieces % 2) * (1 - int(sectionWidth < dist));
}
else {
numPieces = int(floor(restWidth / wallWidth));
}
//filler
float fillerWidth;
if (sectionWidth != 0) {
fillerWidth = (restWidth - ((numPieces * 2) * wallWidth))/2;
}
else {
fillerWidth = (restWidth - ((numPieces) * wallWidth))/2;
}
float fillerScaleX = fillerWidth / wallWidth;
//PLACE PIECES
//place filler pieces
vector tempPos;
vector tempStartPos;
vector tempEndPos;
vector pillarStartPos;
int tempPt;
int pieceIndex = chi('../../mfloor_wframe_index') % len(wallPieceArray);
string piece = wallPieceArray[pieceIndex];
string pieceRef = '../../'+ piece;
if(fillerWidth != 0) {
if(dist > wallWidth) {
tempPos = startPos + ((fillerWidth/2) * dir);
//Start Filler
tempPt = addpoint(geoself(), tempPos);
tempStartPos = tempPos;
setpointattrib(geoself(), 'instance', tempPt, wallRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
//End Pos
tempPt = addpoint(geoself(), endPos - ((fillerWidth/2) * dir));
tempEndPos = endPos - ((fillerWidth/2) * dir);
setpointattrib(geoself(), 'instance', tempPt, wallRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
else {
tempPos = startPos + (fillerWidth * dir);
tempPos = startPos + (fillerWidth * dir);
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, wallRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX * 2,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
//place outside wall tiles
if (numPieces != 0){
if(sectionWidth != 0){
for (int p = 0; p < numPieces; p++){
//place a tile both at the start and end of the wall
tempPos = tempStartPos + (wallWidth * dir)/2 + (wallWidth * dir) * p + (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, pieceRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
if (p == numPieces - 1) {
pillarStartPos = tempPos;
}
addWindows(windowsRd, windowsSq, piece, n, tempPos);
tempPos = tempEndPos - (wallWidth * dir)/2 - (wallWidth * dir) * p - (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, pieceRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
addWindows(windowsRd, windowsSq, piece, n, tempPos);
}
}
else {
for (int p = 0; p < numPieces; p++) {
tempPos = tempStartPos + (wallWidth * dir)/2 + (wallWidth * dir) * p + (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, pieceRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
addWindows(windowsRd, windowsSq, piece, n, tempPos);
}
}
}
//if no wall tiles get placed
else {
pillarStartPos = tempPos + (fillerWidth * dir)/2;
}
//place pillar sections
if(numSections != 0){
printf('Pillar Start Pos: %e \n', pillarStartPos);
printf('NumPieces: %g \n', numPieces);
for (int pl = 0; pl < numPillars; pl++){
vector pillarPos;
if(numPieces != 0){
pillarPos = (pillarStartPos + (wallWidth * dir)/2 + (pillarWidth * dir)/2 + (pillarWidth * dir) * pl) + ((numWallPieces * wallWidth)*dir)*pl;
}
else {
pillarPos = (pillarStartPos + (pillarWidth * dir)/2 + (pillarWidth * dir) * pl) + ((numWallPieces * wallWidth)*dir)*pl;
}
tempPt = addpoint(geoself(), pillarPos);
setpointattrib(geoself(), 'instance', tempPt, pillarRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
for (int sWall = 0; sWall < numWallPieces; sWall++){
if(pl < numPillars - 1){
vector wallPos = pillarPos + (pillarWidth * dir)/2 + (wallWidth * dir)/2 + (wallWidth * dir)*sWall;
tempPt = addpoint(geoself(), wallPos);
setpointattrib(geoself(), 'instance', tempPt, pieceRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
addWindows(windowsRd, windowsSq, piece, n, wallPos);
}
}
}
}
}
}
I originally also thought about somehow calculating the maximum amount of pillars, with the given number of in between pieces, but for now I left it to just fill the wall with tiles, if the specified pillar amount doesn't fit.
I also, similar to the corner pieces, added Parameters to switch through the window frame, window, and pillar meshes.


3 - Floor and Roof Separation

After having figured out the basics for the corners and walls, I moved on to implementing multiple floors, including the option to either add separation pieces to every floor, or to add them only to specific floors.
For the top floor it automatically uses the roof separation tiles, instead of the floor separation ones.

When adding the corners to each row of the building, I also added an attribute to the points, depending on whether it was a wall, floor separation or roof separation row, so I could add the corresponding tiles, when adding the wall sections.

Here are the updated code bits for corners and walls.

#include <math.h>
//VARIABLES
int points[] = primpoints(geoself(), 0);
int numCorners = len(points);
append(points, 0);
vector pointPos[];
vector edgeDir[];
float cornerAngles[];
vector cornerNormals[];
float cornerXSizes[];
//FLOORS AND SEPARATION LISTS
int numFloors = chi('../../numFloors');
// Add Separation after each Floor
int sepForEachFloor = chi('../../chk_floor_sep');
// Add Separation --> only applicable, if sepForEachFloor == 0
int numFloorSep = chi('../../mp_separation');
int floorSepIndices[];
for (int i = 1; i <= numFloorSep; i++){
int floorIndex = chi('../../sep_floor_num'+itoa(i));
int inArray = find(floorSepIndices, floorIndex);
printf('%g is in Array: %g \n', floorIndex, inArray);
if (inArray < 0) {
append(floorSepIndices, floorIndex);
}
}
//MFLOOR CORNER PIECES
string corner_90_param = chs("../kit_storage/mfloor_kit_corner_90");
string corner_90_pieces[] = split(corner_90_param);
string corner_45_param = chs("../kit_storage/mfloor_kit_corner_45");
string corner_45_pieces[] = split(corner_45_param);
//set corner pieces
int cornerIndex = (chi('../../mfloor_corner_index')) % len(corner_90_pieces);
string default_corner_45 = corner_45_pieces[cornerIndex];
string default_corner_90 = corner_90_pieces[cornerIndex];
float wallHeight = getbbox_size(chs("../../" + default_corner_90 + "/" + default_corner_90 + "/file"))[1];
//SEPARATION CORNER PIECES
//fsep
string fsep_corner_45 = chs('../kit_storage/fsep_kit_corner_45');
string fsep_corner_90 = chs('../kit_storage/fsep_kit_corner_90');
string fsep_45_array[] = split(fsep_corner_45);
string fsep_90_array[] = split(fsep_corner_90);
//rsep
string rsep_corner_45 = chs('../kit_storage/rsep_kit_corner_45');
string rsep_corner_90 = chs('../kit_storage/rsep_kit_corner_90');
string rsep_45_array[] = split(rsep_corner_45);
string rsep_90_array[] = split(rsep_corner_90);
//set fsep corner pieces
int fSepIndex = (chi('../../fsep_index')) % len(fsep_90_array);
int rSepIndex = (chi('../../rsep_index')) % len(rsep_90_array);
string fsep_45 = fsep_45_array[fSepIndex];
string fsep_90 = fsep_90_array[fSepIndex];
string rsep_45 = rsep_45_array[rSepIndex];
string rsep_90 = rsep_90_array[rSepIndex];
float fSepHeight = getbbox_size(chs("../../" + fsep_90 + "/" + fsep_90 + "/file"))[1];
float rSepHeight = getbbox_size(chs("../../" + rsep_90 + "/" + rsep_90 + "/file"))[1];
float sepHeight = fSepHeight;
//rsep
//get point positions
for(int i = 0; i < len(points); i++) {
vector newPos = pointattrib(geoself(), "P", points[i], 1);
pointPos[i] = newPos;
//printf('%g: %e \n', points[i], pointPos[i]);
}
//Direction Vectors between Points
for(int i = 0; i < len(points)-1; i++) {
vector dir = normalize(pointPos[i+1]-pointPos[i]);
edgeDir[i] = dir;
//printf('%g - %g, Vector: %e \n', points[i], points[i+1], edgeDir[i]);
}
append(edgeDir, edgeDir[0]);
//get angles
for(int i = 0; i < len(edgeDir) - 1; i++){
int ptIndex = (i+1) % numCorners;
vector v1 = set(edgeDir[i][0], 0, edgeDir[i][2]);
vector v2 = set(edgeDir[i + 1][0], 0, edgeDir[i + 1][2]);
float angle = atan2(v2[2], v2[0])-atan2(v1[2], v1[0]);
if(angle > PI) {
angle -= 2 * PI;
}
else if(angle <= PI * -1) {
angle += 2 * PI;
}
angle = degrees(angle);
vector n;
//90 degree corners
if(abs(rint(angle)) >= 89 && abs(rint(angle)) <= 91){
n = v2 * -1;
if(angle < 0) {
float theta = radians(-90);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
setpointattrib(geoself(), "N", ptIndex, n, "set");
vector dim_max = getbbox_max(chs("../../" + default_corner_90 + "/" + default_corner_90 + "/file"));
float x_max = dim_max[0];
setattrib(geoself(), "point", "piece_x_size", ptIndex, 0, x_max, "set");
cornerXSizes[ptIndex] = x_max;
cornerAngles[ptIndex] = 90;
}
//45 degree corners
else if(abs(rint(angle)) >= 44 && abs(rint(angle)) <= 46){
n = v2 * -1;
if(angle < 0) {
float theta = radians(-90);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
else {
float theta = radians(45);
float cs = cos(theta);
float sn = sin(theta);
float px = n[0] * cs - n[2] * sn;
float pz = n[0] * sn + n[2] * cs;
n = set(px, n[1], pz);
}
setpointattrib(geoself(), "N", ptIndex, n, "set");
vector dim_max = getbbox_max(chs("../../" + default_corner_45 + "/" + default_corner_45 + "/file"));
float x_max = dim_max[0];
setattrib(geoself(), "point", "piece_x_size", ptIndex, 0, x_max, "set");
cornerXSizes[ptIndex] = x_max;
cornerAngles[ptIndex] = 45;
}
cornerNormals[ptIndex] = n;
}
// vector dim_max = getbbox_max(chs("../../" + default_corner_90 + "/" + default_corner_90 + "/file"));
// float x_max = dim_max[0];
// setattrib(geoself(), "point", "piece_x_size", ptIndex, 0, x_max, "set");
if(numFloors >= 1) {
float heightOffset = 0;
int useSep = 0;
//loop through floors
for (int f = 1; f <= numFloors; f++) {
string sep_type = 'floor_sep';
string sep_45 = fsep_45;
string sep_90 = fsep_90;
sepHeight = fSepHeight;
if(f == numFloors) {
sep_type = 'roof_sep';
sep_45 = rsep_45;
sep_90 = rsep_90;
sepHeight = rSepHeight;
}
if( f == 1 ) {
for ( int c = 0; c < numCorners; c++ ){
if (rint(cornerAngles[c]) == 90){
setpointattrib(geoself(), "instance", c, "../../" + default_corner_90, "set");
setpointattrib(geoself(), "row_type", c, 'floor_wall', "set");
}
else if (rint(cornerAngles[c]) == 45){
setpointattrib(geoself(), "instance", c, "../../" + default_corner_45, "set");
setpointattrib(geoself(), "row_type", c, 'floor_wall', "set");
}
}
//separation
if ( sepForEachFloor || (find(floorSepIndices, f) >= 0)) {
useSep = 1;
for ( int c = 0; c < numCorners; c++ ) {
vector tempPos = pointattrib(geoself(), 'P', points[c], 0);
tempPos = set(tempPos[0], wallHeight, tempPos[2]);
int pt = addpoint(geoself(), tempPos);
if (rint(cornerAngles[c]) == 90){
float dim = getbbox_max(chs("../../" + sep_90 + "/" + sep_90 + "/file"))[0];
setpointattrib(geoself(), "instance", pt, "../../" + sep_90, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, dim, "set");
setpointattrib(geoself(), "row_type", pt, sep_type, "set");
}
else if (rint(cornerAngles[c]) == 45){
float dim = getbbox_max(chs("../../" + sep_45 + "/" + sep_45 + "/file"))[0];
setpointattrib(geoself(), "instance", pt, "../../" + sep_45, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, dim, "set");
setpointattrib(geoself(), "row_type", pt, sep_type, "set");
}
}
}
else {
useSep = 0;
}
heightOffset += wallHeight + sepHeight * useSep;
}
else if ( f > 1 ) {
for ( int c = 0; c < numCorners; c++ ){
vector tempPos = pointattrib(geoself(), 'P', points[c], 0);
tempPos = set(tempPos[0], heightOffset, tempPos[2]);
int pt = addpoint(geoself(), tempPos);
if (rint(cornerAngles[c]) == 90){
setpointattrib(geoself(), "instance", pt, "../../" + default_corner_90, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, cornerXSizes[c], "set");
setpointattrib(geoself(), "row_type", pt, 'floor_wall', "set");
}
else if (rint(cornerAngles[c]) == 45){
setpointattrib(geoself(), "instance", pt, "../../" + default_corner_45, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, cornerXSizes[c], "set");
setpointattrib(geoself(), "row_type", pt, 'floor_wall', "set");
}
}
//separation
if ( sepForEachFloor || (find(floorSepIndices, f) >= 0)) {
useSep = 1;
for ( int c = 0; c < numCorners; c++ ) {
vector tempPos = pointattrib(geoself(), 'P', points[c], 0);
tempPos = set(tempPos[0], heightOffset + wallHeight, tempPos[2]);
int pt = addpoint(geoself(), tempPos);
if (rint(cornerAngles[c]) == 90){
float dim = getbbox_max(chs("../../" + sep_90 + "/" + sep_90 + "/file"))[0];
setpointattrib(geoself(), "instance", pt, "../../" + sep_90, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, dim, "set");
setpointattrib(geoself(), "row_type", pt, sep_type, "set");
}
else if (rint(cornerAngles[c]) == 45){
float dim = getbbox_max(chs("../../" + sep_45 + "/" + sep_45 + "/file"))[0];
setpointattrib(geoself(), "instance", pt, "../../" + sep_45, "set");
setpointattrib(geoself(), "N", pt, cornerNormals[c], "set");
setpointattrib(geoself(), "scale", pt, set(1,1,1), "set");
setpointattrib(geoself(), "piece_x_size", pt, dim, "set");
setpointattrib(geoself(), "row_type", pt, sep_type, "set");
}
}
}
else {
useSep = 0;
}
heightOffset += wallHeight + sepHeight * useSep;
}
useSep = 0;
}
}

//VARIABLES
int points[] = primpoints(geoself(), 0);
int numPtFloor = len(points);
int numPt = @numpt;
int numRows = numPt / numPtFloor;
//modular kit piece arrays
//pillars
string pillars = chs('../kit_storage/mfloor_kit_pillar');
string pillarArray[] = split(pillars);
int pillarIndex = int(chi('../../mfloor_pillar_index')%len(pillarArray));
string pillarRef = '../../'+ pillarArray[pillarIndex];
int numPillars = chi('../../num_pillars');
float pillarWidth = getbbox_size(chs('../../' + pillarArray[0] + '/' + pillarArray[0] + '/file' ))[0];
//plain wall
string wall = chs('../kit_storage/mfloor_kit_wall');
string wallRef = '../../'+ wall;
float wallWidth = getbbox_size(chs('../../' + wall + '/' + wall + '/file' ))[0];
//wall pieces
string wallPieces = chs('../kit_storage/mfloor_kit_ww');
string wallPieceArray[] = split(wallPieces);
int pieceIndex = chi('../../mfloor_wframe_index') % len(wallPieceArray);
string piece = wallPieceArray[pieceIndex];
string pieceRef = '../../'+ piece;
int numWallPieces = chi('../../num_pieces_btwn_pillars');
//windows
string windows = chs('../kit_storage/mfloor_kit_window');
string windowArray[] = split(windows);
string windowsRd[], windowsSq[];
//sort windows
for(int w = 0; w < len(windowArray); w++){
if ( match('*rd*', tolower(windowArray[w])) == 1){
append(windowsRd, windowArray[w]);
}
else if ( match('*sq*', tolower(windowArray[w])) == 1){
append(windowsSq, windowArray[w]);
}
}
// floor separation
string fSep = chs('../kit_storage/fsep_kit_wall');
string fSepPillar = chs('../kit_storage/fsep_kit_pillar');
string fSepArray[] = split(fSep);
string fSepPillarArray[] = split(fSepPillar);
string fSepRef = '../../' + fSepArray[0];
string fSepPillarRef = '../../' + fSepPillarArray[0];
// roof separation
string rSep = chs('../kit_storage/rsep_kit_wall');
string rSepArray[] = split(rSep);
int rSepIndex = chi('../../rsep_index') % len(rSepArray);
string rSepRef = '../../' + rSepArray[rSepIndex];
//Function for adding windows to the Window Frames
void addWindows (string wRd[]; string wSq[]; string wPiece; vector wNormal; vector wPos) {
int wIndex = chi('../../mfloor_window_index') % len(wRd);
int wPt;
string wRef;
if (match('*rd*', tolower(wPiece))) {
wRef = '../../' + wRd[wIndex];
wPt = addpoint(geoself(), wPos);
setpointattrib(geoself(), 'instance', wPt, wRef, 'set');
setpointattrib(geoself(), 'scale', wPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', wPt, wNormal, 'set');
}
else if (match('*sq*', tolower(wPiece))){
wRef = '../../' + wSq[wIndex];
wPt = addpoint(geoself(), wPos);
setpointattrib(geoself(), 'instance', wPt, wRef, 'set');
setpointattrib(geoself(), 'scale', wPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', wPt, wNormal, 'set');
}
}
int numFloors = chi('../../numFloors');
//PLACE WALL SECTIONS
if (numFloors > 0) {
for (int r = 0; r < numRows; r++) { //loop through wall and separation rows
//get points belonging to each row
int rowPoints[] = {};
int start = r * numPtFloor;
for ( int c = 0; c < numPtFloor; c++ ) {
append(rowPoints, start + c);
}
append(rowPoints, start);
string rowType = pointattrib(geoself(), 'row_type', rowPoints[0], 1);
//place wall tiles
for (int c = 0; c < len(rowPoints)-1; c++) { //loop through corner points
//PREP
// start and end point position
vector startPointPos = pointattrib(geoself(), 'P', rowPoints[c], 1);
vector endPointPos = pointattrib(geoself(), 'P', rowPoints[c + 1], 1);
vector dir = normalize(endPointPos - startPointPos);
// start and end position, taking corner piece width into consideration
vector startPos = startPointPos + (pointattrib(geoself(), 'piece_x_size', rowPoints[c], 1)*dir);
vector endPos = endPointPos - (pointattrib(geoself(), 'piece_x_size', rowPoints[c + 1], 1)*dir);
//normal for placing the pieces on the edges
vector n = set(dir[2], 0, dir[0] * -1);
float dist = distance(startPos, endPos);
//only place tiles, if corner pieces don't overlap
if ( (pointattrib(geoself(), 'piece_x_size', rowPoints[c], 1) + pointattrib(geoself(), 'piece_x_size', rowPoints[c + 1], 1)) < distance(startPointPos, endPointPos) ) {
//CALCULATE SECTIONS
float sectionWidth = (numWallPieces * wallWidth) * clamp(numPillars - 1, 0, 10) + numPillars * pillarWidth;
sectionWidth *= int(sectionWidth < dist);
int numSections;
numSections = int((sectionWidth != 0));
numWallPieces *= int(numPillars > 1);
//num tiles on each side of pillar section
float restWidth = dist - sectionWidth;
int numPieces;
if (sectionWidth != 0) {
numPieces = int(floor((restWidth/2)/wallWidth));
numPieces -= (numPieces % 2) * (1 - int(sectionWidth < dist));
}
else {
numPieces = int(floor(restWidth / wallWidth));
}
//filler
float fillerWidth;
if (sectionWidth != 0) {
fillerWidth = (restWidth - ((numPieces * 2) * wallWidth))/2;
}
else {
fillerWidth = (restWidth - ((numPieces) * wallWidth))/2;
}
float fillerScaleX = fillerWidth / wallWidth;
//PLACE PIECES
//place filler pieces
vector tempPos;
vector tempStartPos;
vector tempEndPos;
vector pillarStartPos;
int tempPt;
if (fillerWidth != 0) {
string fillerRef = wallRef;
if (rowType == 'floor_wall'){
fillerRef = wallRef;
}
else if (rowType == 'floor_sep'){
fillerRef = fSepRef;
}
else if (rowType == 'roof_sep'){
fillerRef = rSepRef;
}
if(dist > wallWidth) {
tempPos = startPos + ((fillerWidth/2) * dir);
//Start Filler
tempPt = addpoint(geoself(), tempPos);
tempStartPos = tempPos;
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
//End Pos
tempPt = addpoint(geoself(), endPos - ((fillerWidth/2) * dir));
tempEndPos = endPos - ((fillerWidth/2) * dir);
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
else {
tempPos = startPos + (fillerWidth * dir);
tempPos = startPos + (fillerWidth * dir);
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, fillerRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(fillerScaleX * 2,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
}
}
//place outside wall tiles
if (numPieces != 0){
string spareRef = pieceRef;
if (rowType == 'floor_wall'){
spareRef = pieceRef;
}
else if (rowType == 'floor_sep'){
spareRef = fSepRef;
}
else if (rowType == 'roof_sep'){
spareRef = rSepRef;
}
if(sectionWidth != 0){
for (int p = 0; p < numPieces; p++){
//place a tile both at the start and end of the wall
tempPos = tempStartPos + (wallWidth * dir)/2 + (wallWidth * dir) * p + (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
if (p == numPieces - 1) {
pillarStartPos = tempPos;
}
if ( rowType == 'floor_wall') {
addWindows(windowsRd, windowsSq, piece, n, tempPos);
}
tempPos = tempEndPos - (wallWidth * dir)/2 - (wallWidth * dir) * p - (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
if ( rowType == 'floor_wall') {
addWindows(windowsRd, windowsSq, piece, n, tempPos);
}
}
}
else {
for (int p = 0; p < numPieces; p++) {
tempPos = tempStartPos + (wallWidth * dir)/2 + (wallWidth * dir) * p + (fillerWidth * dir)/2;
tempPt = addpoint(geoself(), tempPos);
setpointattrib(geoself(), 'instance', tempPt, spareRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
if ( rowType == 'floor_wall') {
addWindows(windowsRd, windowsSq, piece, n, tempPos);
}
}
}
}
//if no wall tiles get placed
else {
pillarStartPos = tempPos + (fillerWidth * dir)/2;
}
//place pillar sections
if(numSections != 0){
string wpRef = pieceRef;
string pRef = pillarRef;
float xScale = 1;
if (rowType == 'floor_wall'){
wpRef = pieceRef;
pRef = pillarRef;
xScale = 1;
}
else if (rowType == 'floor_sep'){
wpRef = fSepRef;
pRef = fSepPillarRef;
xScale = 1;
}
else if (rowType == 'roof_sep'){
wpRef = rSepRef;
pRef = rSepRef;
xScale = pillarWidth / wallWidth;
}
printf('Pillar Start Pos: %e \n', pillarStartPos);
printf('NumPieces: %g \n', numPieces);
for (int pl = 0; pl < numPillars; pl++){
vector pillarPos;
if(numPieces != 0){
pillarPos = (pillarStartPos + (wallWidth * dir)/2 + (pillarWidth * dir)/2 + (pillarWidth * dir) * pl) + ((numWallPieces * wallWidth)*dir)*pl;
}
else {
pillarPos = (pillarStartPos + (pillarWidth * dir)/2 + (pillarWidth * dir) * pl) + ((numWallPieces * wallWidth)*dir)*pl;
}
tempPt = addpoint(geoself(), pillarPos);
setpointattrib(geoself(), 'instance', tempPt, pRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(xScale,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
for (int sWall = 0; sWall < numWallPieces; sWall++){
if(pl < numPillars - 1){
vector wallPos = pillarPos + (pillarWidth * dir)/2 + (wallWidth * dir)/2 + (wallWidth * dir) * sWall;
tempPt = addpoint(geoself(), wallPos);
setpointattrib(geoself(), 'instance', tempPt, wpRef, 'set');
setpointattrib(geoself(), 'scale', tempPt, set(1,1,1), 'set');
setpointattrib(geoself(), 'N', tempPt, n, 'set');
if (rowType == 'floor_wall') {
addWindows(windowsRd, windowsSq, piece, n, wallPos);
}
}
}
}
}
} //end if
} // end for c
} // end for r
}


I also brought the HDA into Unreal and it seems to be working alright, but we really need to figure out LODs, otherwise we sacrifice performance, where it is not necessary.


4 - Roof Mesh

Since the roof at that point was just a hole and might have caused issues with lighting (leaving aside, that in a game the rooftop might be visible), I needed to create a mesh from the footprint curve, which I had to do in a separate geometry node. Since Mike explained the use and advantages of detail attributes to me, I thought I could store the point positions of the curve in a detail attribute and use it to recreate the curve. I had some trouble figuring out how to get the attribute reference work with the coordinate parameter of the new curve, so I just used python, modified the attribute to fit the pattern of the coordinates and it ended up working.

node = hou.pwd()
geo = node.geometry()
# Add code to modify contents of geo.
# Use drop down menu to select examples.
nGeo = hou.node('../../Instance/output0/').geometry()
#print(nGeo.findGlobalAttrib('mfloor_curve_points'))
#print(nGeo.attribValue('mfloor_curve_points'))
curvePoints = nGeo.floatListAttribValue('mfloor_curve_points')
mfloorHeight = nGeo.floatAttribValue('mfloor_height_total')
print mfloorHeight
def listChunks(l, n):
for i in range(0, len(l), n):
yield l[i: i+n]
pointList = list(listChunks(curvePoints, 3))
coordinateString = ''
for i in pointList:
coordinateString += str(i)[1:-1] + ' '
curveNode = hou.node('../mfloor_roof')
curveNode.parm('coords').set(coordinateString)
transformNode = hou.node('../transform_height_offset')
transformNode.parm('ty').set(mfloorHeight)

After using a divide, poly-extrude and unwrap node, I ended up with a pretty usable roof mesh. I also stored the height of the mid floor section in a detail attribute and used it to offset it.

Complete Middle Floor Section

5 - To Do

  1. Fix Import and Read .json file python code
  2. Add Ground Floor Section
  3. Figure out Material IDs !!
  4. LODs
  5. Collision

Monday, 3 February 2020

Week 4 - Json Files Part 2

After spending the majority of time in week 3 working on the json file generator tool, I decided that I had to go back into Houdini and get things moving in there. So week 4 was basically implementing the use of the .json files in the building generator.

1 - Importing .FBX Files

EDIT: Turns out there is some issue with the FBX import, when creating Digital Assets for Unreal, as the axis of the meshed don't get updated in UE4. It works, when the .FBX files get imported with the geometry option. I might see, if I can later figure out another solution, but at the moment I need to move on.

First I worked on automating the import process, by using a .json file containing all the .FBX files of the modular kit. I used Python to read the file, import of reimport from the specified path, depending on whether a geometry node of the same name already exists or not. I then stored all the node information in a parameter, so other nodes can access it. I also had to copy those geometry nodes into the subnet, that contains all the nodes for the Digital Asset and delete the other ones. At the moment this is done in two python nodes, however I will probably move them into one later.

node = hou.pwd()
import json
# Add code to modify contained geometries.
# Use drop down menu to select examples.
def onClick():
filepath = node.parm('import_file').eval()
kitNodes = []
kitNodeNames = []
newNodes = []
importNode = hou.node('/obj')
#check existing kit files
parentNode = node.parent()
children = parentNode.children()
for c in children:
t = str(c.type().name())
if(t == 'geo'):
kitNodes.append(c)
kitNodeNames.append(str(c))
#read data and import
with open(filepath) as myfile:
data = myfile.read()
kit = json.loads(data)
for obj in kit:
if(obj['name'] in kitNodeNames):
#if file already exists, reload mesh
hou.parm('../' + obj['name'] + '/' + obj['name'] + '/file').set(obj['path'])
hou.parm('../' + obj['name'] + '/' + obj['name'] + '/reload').pressButton()
print(obj['name'] + ' reloaded')
else:
#import file and move into subnet
hou.hipFile.importFBX(obj['path'], unlock_geometry = True, unlock_deformations = True, import_into_object_subnet = False, convert_into_y_up_coordinate_system = True)
n = hou.node('/obj/'+obj['name'])
n.setDisplayFlag(False)
print(obj['name'] + ' imported')
newNodes.append(n)
hou.copyNodesTo(newNodes, parentNode)
for n in newNodes:
n.destroy()
node = hou.pwd()
# Add code to modify contained geometries.
# Use drop down menu to select examples.
parentNode = node.parent()
children = parentNode.children()
cPaths = ''
for c in children:
if(str(c.type().name()) == 'geo'):
cPaths += str(c.path()) + ' '
#print(hou.node(c.path()))
n = hou.node('../Import_FBX_Files')
n.parm('kit_pieces').set(cPaths)


2 - Evaluating Kit Files

After finishing the import bit I now had to figure out, how to work with the kit .json files. I created a null node for storing the parameters. This looks like this:

Kit info storage
Essentially the code checks, if there is a file in the kit file slots and depending on that either sets the corresponding parameters to '' or evaluates the .json file (I might add a check for file type to make sure it is a json file.)
If it evaluates the .json file, it checks for piece types in the file, e.g. corners, walls, etc., and filters the kit pieces based on their type and saves them into parameters. It also checks, if the geometry file for each piece exists and currently gives a warning, if it doesn't. I might change it, so that the missing files do get imported, or just get rid of the import section and just import the pieces on evaluating the kit files, might make more sense and gets rid of an extra button click. For the parameters it either creates a new parameter, if it doesn't exist, yet or overwrites it's value, if it exists. Again, if a parameter is not needed, the value is set to ''. I originally tried deleting those parameters, but ran into some problems, so I decided to solve it this way.

import json
node = hou.pwd()
# Add code to modify contents of geo.
# Use drop down menu to select examples.
def onClick():
parentNode = hou.node('/obj/UE4_Building_Generator/')
parentParms = parentNode.parms()
storageNode = hou.node('/obj/UE4_Building_Generator/Instance/kit_storage')
storageParms = storageNode.parms()
for p in parentParms:
if ('_kit' in p.name()):
if str(p.eval()) == '':
print('Kit not in use')
kitName = str(p.name())
for param in storageParms:
if(kitName in str(param.name())):
print (param.name(), ' not used')
storageNode.parm(str(param.name())).set('')
elif str(p.eval()) != '' :
filepath = p.eval()
kitName = p.name()
#read data from json files
with open (filepath) as file:
data = file.read()
kit = json.loads(data)
types = []
newParmNames = []
allTypeKits = {}
#filter types
for obj in kit:
if (str(obj['type']) in types) == False :
types.append(str(obj['type']))
#sort kit pieces by type
for t in types:
#create new Parameter Names list
parmName = kitName + '_' + t
newParmNames.append(parmName)
print(parmName)
for t in types:
typeKit = ''
for obj in kit:
#check if geometry file exists
n = hou.node('/obj/UE4_Building_Generator/'+str(obj['name']))
if(n != None):
if (str(obj['type']) == t):
typeKit += str(n.path()) + ' '
#SHOULD I IMPORT MISSING FBX FILE and add to pieceList?
else:
print('Geometry for ' + str(obj['name'] + ' has not been imported.'))
keyName = kitName + '_' + t
allTypeKits.update({keyName : typeKit})
#print('All Type Kits: ' + str(allTypeKits))
print('New Param Names: ' + str(newParmNames))
for newParm in newParmNames:
p = storageNode.parm(newParm)
if(p == None):
print('Create param: ' + newParm)
ptg = storageNode.parmTemplateGroup()
strParm = hou.StringParmTemplate(newParm, newParm, 1, string_type = hou.stringParmType.NodeReferenceList)
ptg.append(strParm)
storageNode.setParmTemplateGroup(ptg)
storageNode.parm(newParm).set(allTypeKits[newParm])
else:
print('New value for: ' + newParm)
storageNode.parm(str(newParm)).set(allTypeKits[newParm])
#if parameter not in use: set to ''
for param in storageParms:
if(kitName in str(param.name())):
if ((str(param.name()) not in newParmNames) == True):
print (param.name(), ' not used')
storageNode.parm(str(param.name())).set('')
print('\n')


I made a small video, showing how it currently works.



Again, all the code bits will probably need some optimisation, but I need to move on to the actual building generator bit and clean up the code later. It kinda feels like I did not get a lot done last week, a lot of my current work involves hours of digging through documentations and planning, but now that I have all the necessary modular kit organisation  parts, I will go back to what I have already made of the building generator and  continue it.