#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; namespace GSpawn { public enum BoxObjectSpawnHeightPatternDirection { LeftToRight = 0, RightToLeft, BackToFront, FrontToBack } public class BoxObjectSpawn : ObjectSpawnTool { [SerializeField] private ObjectSpawnExtensionPlane _extensionPlane = new ObjectSpawnExtensionPlane(); [SerializeField] private BoxObjectSpawnHeightPatternDirection _heightPatternDirection = BoxObjectSpawnHeightPatternDirection.LeftToRight; [NonSerialized] private ObjectSpawnCellBox _cellBox; [NonSerialized] private bool _isBuildingBox = false; [NonSerialized] private int _currentHeight; [NonSerialized] private OBB _refOBB; [NonSerialized] private ObjectOverlapFilter _overlapFilter = new ObjectOverlapFilter(); [NonSerialized] private List _heightPattern = new List(); [NonSerialized] private IntPatternSampler _heightPatternSampler = null; [NonSerialized] private TerrainCollection _terrainCollection = new TerrainCollection(); [NonSerialized] private List _gameObjectBuffer = new List(); [NonSerialized] private List _allChildrenAndSeflBuffer = new List(); [NonSerialized] private List _cellOBBBuffer = new List(); [NonSerialized] private List _mirroredCellOBBBuffer = new List(); [NonSerialized] private List _mirroredObjectListBuffer = new List(); [NonSerialized] private ObjectModularSnapSettings _modularSnapSettings; [SerializeField] private ObjectModularSnapSession _modularSnapSession; [NonSerialized] private SceneRaycastFilter _pickPrefabRaycastFilter; [NonSerialized] private ObjectProjectionSettings _terrainProjectionSettings; [SerializeField] private ObjectMirrorGizmo _mirrorGizmo; [NonSerialized] private ObjectMirrorGizmoSettings _mirrorGizmoSettings; private ObjectProjectionSettings terrainProjectionSettings { get { if (_terrainProjectionSettings == null) { _terrainProjectionSettings = CreateInstance(); UndoEx.saveEnabledState(); UndoEx.enabled = false; _terrainProjectionSettings.halfSpace = ObjectProjectionHalfSpace.InFront; _terrainProjectionSettings.embedInSurface = true; _terrainProjectionSettings.alignAxis = false; _terrainProjectionSettings.projectAsUnit = true; UndoEx.restoreEnabledState(); } return _terrainProjectionSettings; } } private BoxObjectSpawnSettingsProfile settings { get { return BoxObjectSpawnSettingsProfileDb.instance.activeProfile; } } private ObjectModularSnapSession modularSnapSession { get { if (_modularSnapSession == null) { _modularSnapSession = CreateInstance(); _modularSnapSession.sharedSettings = modularSnapSettings; } return _modularSnapSession; } } public ObjectModularSnapSettings modularSnapSettings { get { if (_modularSnapSettings == null) _modularSnapSettings = AssetDbEx.loadScriptableObject(PluginFolders.settings, typeof(BoxObjectSpawn).Name + "_" + typeof(ObjectModularSnapSettings).Name); return _modularSnapSettings; } } public ObjectMirrorGizmoSettings mirrorGizmoSettings { get { if (_mirrorGizmoSettings == null) _mirrorGizmoSettings = AssetDbEx.loadScriptableObject(PluginFolders.settings, typeof(BoxObjectSpawn).Name + "_" + typeof(ObjectMirrorGizmoSettings).Name); return _mirrorGizmoSettings; } } public override ObjectSpawnToolId spawnToolId { get { return ObjectSpawnToolId.Box; } } public override bool requiresSpawnGuide { get { return true; } } public override ObjectMirrorGizmo mirrorGizmo { get { return _mirrorGizmo; } } public override bool canChangeSpawnGuideTransform { get { return !_isBuildingBox; } } public bool isBuildingBox { get { return _isBuildingBox; } } public BoxObjectSpawn() { _overlapFilter.customFilter = new Func((GameObject go) => { return !go.isTerrainMesh() && !go.isSphericalMesh(); }); } public override void setSpawnGuidePrefab(PluginPrefab prefab) { onCancelBoxBuild(); spawnGuide.usePrefab(prefab, modularSnapSession); } public override void onNoLongerActive() { onCancelBoxBuild(); spawnGuide.destroyGuide(); } public void executeModularSnapSessionCommand(ObjectModularSnapSessionCommand command) { if (!_isBuildingBox) modularSnapSession.executeCommand(command); } public void nextHeightPatternDirection() { int newDir = ((int)_heightPatternDirection + 1) % Enum.GetValues(typeof(BoxObjectSpawnHeightPatternDirection)).Length; _heightPatternDirection = (BoxObjectSpawnHeightPatternDirection)newDir; updateBox(); EditorUtility.SetDirty(this); } public void previousHeightPatternDirection() { int newDir = ((int)_heightPatternDirection - 1); if (newDir < 0) newDir = Enum.GetValues(typeof(BoxObjectSpawnHeightPatternDirection)).Length - 1; _heightPatternDirection = (BoxObjectSpawnHeightPatternDirection)newDir; updateBox(); EditorUtility.SetDirty(this); } public void nextExtensionPlane() { if (!_isBuildingBox && spawnGuide.isPresentInScene) _extensionPlane.setRefOBBFace(Box3D.getNextFace(_extensionPlane.refOBBFace)); } public void setCurrentHeight(int height) { if (!_isBuildingBox) return; float sizeAlongHeight = Vector3Ex.getSizeAlongAxis(_refOBB.size, _refOBB.rotation, _extensionPlane.planeNormal); if (sizeAlongHeight < 1e-4f) return; int oldHeight = _currentHeight; _currentHeight = height; if (settings.heightMode == BoxObjectSpawnHeightMode.Constant) _cellBox.setHeight(_currentHeight); else if (settings.heightMode == BoxObjectSpawnHeightMode.Random) { int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); var randomHeight = stack.height - oldHeight; stack.setHeight(_currentHeight + randomHeight); } } } else if (settings.heightMode == BoxObjectSpawnHeightMode.Pattern) { updateBoxHeight(_cellBox.size); } applyCornerMode(); applyFillMode(); } public void raiseCurrentHeight() { if (!_isBuildingBox || settings.heightMode != BoxObjectSpawnHeightMode.Constant) return; setCurrentHeight(_currentHeight + settings.heightRaiseAmount); } public void lowerCurrentHeight() { if (!_isBuildingBox || settings.heightMode != BoxObjectSpawnHeightMode.Constant) return; setCurrentHeight(_currentHeight - settings.heightRaiseAmount); } protected override void doOnSceneGUI() { Event e = Event.current; _mirrorGizmo.onSceneGUI(); if (!_isBuildingBox) { spawnGuide.onSceneGUI(); if (FixedShortcuts.enablePickSpawnGuidePrefabFromScene(e)) { if (e.isLeftMouseButtonDownEvent()) { var prefabPickResult = PluginScene.instance.pickPrefab(PluginCamera.camera.getCursorRay(), _pickPrefabRaycastFilter, ObjectRaycastConfig.defaultConfig); if (prefabPickResult != null) { setSpawnGuidePrefab(prefabPickResult.pickedPluginPrefab); spawnGuide.setRotationAndScale(prefabPickResult.pickedObject.transform.rotation, prefabPickResult.pickedObject.transform.lossyScale); } } } if (modularSnapSession.isActive) { updateExtensionPlane(); if (e.isLeftMouseButtonDownEvent()) { if (e.noShiftCtrlCmdAlt()) onBeginBoxBuild(); else { if (FixedShortcuts.extensionPlane_EnablePickOnClick(e)) { GameObjectEx.getAllChildrenAndSelf(spawnGuide.gameObject, false, false, _gameObjectBuffer); _extensionPlane.pickRefOBBFaceWithCursor(_gameObjectBuffer); } } } if (FixedShortcuts.extensionPlane_ChangeByScrollWheel(e)) { e.disable(); if (e.getMouseScrollSign() < 0) _extensionPlane.setRefOBBFace(Box3D.getNextFace(_extensionPlane.refOBBFace)); else _extensionPlane.setRefOBBFace(Box3D.getPreviousFace(_extensionPlane.refOBBFace)); } if (_mirrorGizmo.enabled) { _mirrorGizmo.mirrorOBB(spawnGuide.calcWorldOBB(), _cellOBBBuffer); _mirrorGizmo.drawMirroredOBBs(_cellOBBBuffer); } } } else { if (_mirrorGizmo.enabled) { getAllCellOBBs(_cellOBBBuffer); _mirrorGizmo.mirrorOBBs(_cellOBBBuffer, _mirroredCellOBBBuffer); _mirrorGizmo.drawMirroredOBBs(_mirroredCellOBBBuffer); } if (FixedShortcuts.cancelAction(e) || spawnGuide.gameObject == null) { onCancelBoxBuild(); return; } if (FixedShortcuts.objectSpawnStructure_UpdateHeightByScrollWheel(e)) { e.disable(); if (e.getMouseScrollSign() < 0) setCurrentHeight(_currentHeight + settings.heightRaiseAmount); else setCurrentHeight(_currentHeight - settings.heightLowerAmount); } if (settings.heightMode == BoxObjectSpawnHeightMode.Pattern) { if (FixedShortcuts.boxObjectSpawn_ChangePatternDirectionByScrollWheel(e)) { e.disable(); if (e.getMouseScrollSign() < 0) nextHeightPatternDirection(); else previousHeightPatternDirection(); } } if (e.isLeftMouseButtonDownEvent() && SceneViewEx.containsCursor(e)) onEndBoxBuild(); else if (e.isMouseMoveEvent()) updateBox(); } } protected override void draw() { if (!isSpawnGuidePresentInScene && !_isBuildingBox) return; _extensionPlane.borderColor = ObjectSpawnPrefs.instance.boxSpawnExtensionPlaneBorderColor; _extensionPlane.fillColor = ObjectSpawnPrefs.instance.boxSpawnExtensionPlaneFillColor; _extensionPlane.draw(); if (_cellBox != null) { var drawConfig = new ObjectSpawnCellBox.DrawConfig(); drawConfig.cellWireColor = ObjectSpawnPrefs.instance.boxSpawnCellWireColor; drawConfig.xAxisColor = ObjectSpawnPrefs.instance.boxSpawnXAxisColor; drawConfig.yAxisColor = ObjectSpawnPrefs.instance.boxSpawnYAxisColor; drawConfig.zAxisColor = ObjectSpawnPrefs.instance.boxSpawnZAxisColor; drawConfig.xAxisLength = ObjectSpawnPrefs.instance.boxSpawnXAxisLength; drawConfig.yAxisLength = ObjectSpawnPrefs.instance.boxSpawnYAxisLength; drawConfig.zAxisLength = ObjectSpawnPrefs.instance.boxSpawnZAxisLength; drawConfig.drawInfoText = ObjectSpawnPrefs.instance.boxSpawnShowInfoText; drawConfig.hasUniformHeight = settings.heightMode == BoxObjectSpawnHeightMode.Constant; drawConfig.height = _currentHeight; drawConfig.drawGranular = true; _cellBox.draw(drawConfig); } } protected override void onEnabled() { Undo.undoRedoPerformed += onUndoRedo; _pickPrefabRaycastFilter = createDefaultPrefabPickRaycastFilter(); modularSnapSession.sharedSettings = modularSnapSettings; if (_mirrorGizmo == null) { _mirrorGizmo = ScriptableObject.CreateInstance(); _mirrorGizmo.enabled = false; } _mirrorGizmo.sharedSettings = mirrorGizmoSettings; } protected override void onDisabled() { Undo.undoRedoPerformed -= onUndoRedo; ScriptableObjectEx.destroyImmediate(_modularSnapSession); ScriptableObjectEx.destroyImmediate(_terrainProjectionSettings); } protected override void onDestroy() { ScriptableObjectEx.destroyImmediate(_modularSnapSession); ScriptableObjectEx.destroyImmediate(_terrainProjectionSettings); } private void getAllCellOBBs(List obbs) { obbs.Clear(); int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); int numCells = stack.numCells; for (int cellIndex = 0; cellIndex < numCells; ++cellIndex) { var cell = stack.getCell(cellIndex); if (cell.isGoodForSpawn) obbs.Add(cell.objectOBB); } } } } private void updateExtensionPlane() { _extensionPlane.set(calcSpawnGuideWorldOBB(), _extensionPlane.refOBBFace, ObjectSpawnPrefs.instance.boxSpawnExtensionPlaneInflateAmount); } private void onBeginBoxBuild() { if (settings.heightMode == BoxObjectSpawnHeightMode.Pattern) { _heightPatternSampler = IntPatternSampler.create(settings.heightPatternWrapMode); if (settings.heightPattern.numValues == 0) { Debug.LogWarning("The selected height pattern is empty. The default pattern will be used instead."); IntPatternDb.instance.defaultPattern.getValues(_heightPattern); } else settings.heightPattern.getValues(_heightPattern); } _refOBB = calcRefOBB(); _currentHeight = settings.defaultHeight; _cellBox = new ObjectSpawnCellBox(_refOBB, _extensionPlane.planeNormal, _extensionPlane.right); var oldBoxSize = _cellBox.setSize(1, 1); _cellBox.setHorizontalPadding(settings.horizontalPadding); _cellBox.setVerticalPadding(settings.verticalPadding); updateBoxHeight(oldBoxSize); applyCornerMode(); applyFillMode(); BoxObjectSpawnSettingsProfileDbUI.instance.setEnabled(false); _isBuildingBox = true; spawnGuide.setGuideObjectActive(false); } private void onCancelBoxBuild() { _cellBox = null; BoxObjectSpawnSettingsProfileDbUI.instance.setEnabled(true); _isBuildingBox = false; spawnGuide.setGuideObjectActive(true); } private void onEndBoxBuild() { spawnObjects(); _cellBox = null; BoxObjectSpawnSettingsProfileDbUI.instance.setEnabled(true); _isBuildingBox = false; spawnGuide.setGuideObjectActive(true); } private void updateBox() { if (!_extensionPlane.cursorRaycast()) return; var oldBoxSize = _cellBox.snapSizeAndExtensionAxesToCursor(_extensionPlane, FixedShortcuts.boxObjectSpawn_EnableEqualSize(Event.current), calcBoxMaxSize()); updateBoxHeight(oldBoxSize); applyCornerMode(); applyFillMode(); } private Vector2Int calcBoxMaxSize() { Vector2Int maxSize = new Vector2Int(settings.maxSize, settings.maxSize); if (settings.heightMode == BoxObjectSpawnHeightMode.Pattern && settings.constrainSizeToHeightPattern) { if (_heightPatternDirection == BoxObjectSpawnHeightPatternDirection.LeftToRight || _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.RightToLeft) { if (_heightPattern.Count < settings.maxSize) maxSize.x = _heightPattern.Count; } else { if (_heightPattern.Count < settings.maxSize) maxSize.y = _heightPattern.Count; } } return maxSize; } [NonSerialized] private MirroredObjectList _spawnObjects_MirroredObjectList = new MirroredObjectList(); private void spawnObjects() { skipCellsBeforeSpawn(); _spawnObjects_MirroredObjectList.objects.Clear(); _gameObjectBuffer.Clear(); if (settings.projectionMode == BoxObjectSpawnProjectionMode.Terrains) PluginScene.instance.findAllTerrains(_terrainCollection); bool projectOnTerrains = settings.projectionMode == BoxObjectSpawnProjectionMode.Terrains && (_terrainCollection.unityTerrains.Count != 0 || _terrainCollection.terrainMeshes.Count != 0); int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { ObjectSpawnCellStack stack = _cellBox.getStack(col, row); int numCells = stack.numCells; for (int cellIndex = 0; cellIndex < numCells; ++cellIndex) { ObjectSpawnCell cell = stack.getCell(cellIndex); if (!cell.isGoodForSpawn) continue; GameObject go = spawnObjectInCell(stack, cell, cellIndex); if (go != null) _gameObjectBuffer.Add(go); } if (projectOnTerrains) ObjectProjection.projectHierarchiesOnTerrainsAsUnit(_gameObjectBuffer, _terrainCollection, terrainProjectionSettings); ObjectEvents.onObjectsSpawned(_gameObjectBuffer); // Note: Needed to handle avoidOverlaps with mirroring enabled (bottom of function). if (_mirrorGizmo.enabled) { if (projectOnTerrains) { _mirrorGizmo.mirrorObjectsOrganized_NoDuplicateCommand(_gameObjectBuffer, _mirroredObjectListBuffer); foreach (var list in _mirroredObjectListBuffer) { ObjectProjection.projectHierarchiesOnTerrainsAsUnit(list.objects, _terrainCollection, terrainProjectionSettings); if (settings.avoidOverlaps) _spawnObjects_MirroredObjectList.objects.AddRange(list.objects); } } else { if (settings.avoidOverlaps) _mirrorGizmo.mirrorObjects_NoDuplicateCommand(_gameObjectBuffer, _spawnObjects_MirroredObjectList, true); else _mirrorGizmo.mirrorObjects(_gameObjectBuffer); } } _gameObjectBuffer.Clear(); } } if (settings.avoidOverlaps && _spawnObjects_MirroredObjectList.objects.Count != 0) { prepareSceneObjectOverlapFilter(); ObjectEvents.onObjectsSpawned(_spawnObjects_MirroredObjectList.objects); destroyObjectsWhichOverlapScene(_spawnObjects_MirroredObjectList.objects); } } private void destroyObjectsWhichOverlapScene(List gameObjects) { ObjectOverlapConfig overlapConfig = ObjectOverlapConfig.defaultConfig; ObjectBounds.QueryConfig boundsQConfig = ObjectBounds.QueryConfig.defaultConfig; boundsQConfig.objectTypes = GameObjectType.Mesh | GameObjectType.Sprite; var oldIgnoredHierarchy = _overlapFilter.ignoredHierarchy; for (int i = 0; i < gameObjects.Count;) { GameObject go = gameObjects[i]; OBB obb = ObjectBounds.calcHierarchyWorldOBB(go, boundsQConfig); if (obb.isValid) { _overlapFilter.ignoredHierarchy = go; obb.inflate(-1e-2f); if (PluginScene.instance.overlapBox(obb, _overlapFilter, overlapConfig)) { ObjectEvents.onObjectWillBeDestroyed(go); GameObject.DestroyImmediate(go); gameObjects.RemoveAt(i); continue; } } ++i; } _overlapFilter.ignoredHierarchy = oldIgnoredHierarchy; } private GameObject spawnObjectInCell(ObjectSpawnCellStack stack, ObjectSpawnCell cell, int cellIndex) { if (settings.prefabPickMode == BoxObjectSpawnPrefabPickMode.SpawnGuide) { Vector3 objectPos = ObjectPositionCalculator.calcRootPosition(spawnGuide.gameObject, _refOBB, cell.objectOBBCenter, spawnGuide.lossyScale, cell.objectOBBRotation); return spawnGuide.spawn(objectPos, cell.objectOBBRotation, spawnGuide.lossyScale); } else if (settings.prefabPickMode == BoxObjectSpawnPrefabPickMode.Random) { var pool = settings.randomPrefabProfile; RandomPrefab prefab = pool.pickPrefab(); if (prefab != null) { PluginPrefab pluginPrefab = prefab.pluginPrefab; GameObject prefabAsset = pluginPrefab.prefabAsset; Vector3 objectPos = ObjectPositionCalculator.calcRootPosition(prefabAsset, ObjectBounds.calcHierarchyWorldOBB(prefabAsset, spawnGuide.worldOBBQConfig), cell.objectOBBCenter, prefabAsset.transform.lossyScale, cell.objectOBBRotation); return pluginPrefab.spawn(objectPos, cell.objectOBBRotation, prefabAsset.transform.lossyScale); } } else if (settings.prefabPickMode == BoxObjectSpawnPrefabPickMode.HeightRange) { var pool = settings.heightRangePrefabProfile; IntRangePrefab prefab = pool.pickPrefab(stack.cellIndexToHeight(cellIndex)); if (prefab != null) { PluginPrefab pluginPrefab = prefab.pluginPrefab; GameObject prefabAsset = pluginPrefab.prefabAsset; Vector3 objectPos = ObjectPositionCalculator.calcRootPosition(prefabAsset, ObjectBounds.calcHierarchyWorldOBB(prefabAsset, spawnGuide.worldOBBQConfig), cell.objectOBBCenter, prefabAsset.transform.lossyScale, cell.objectOBBRotation); return pluginPrefab.spawn(objectPos, cell.objectOBBRotation, prefabAsset.transform.lossyScale); } } return null; } private float getOverlapCheckOBBInflateAmount() { return -1e-1f; } private ObjectOverlapConfig getOverlapCheckConfig() { return ObjectOverlapConfig.defaultConfig; } private void prepareSceneObjectOverlapFilter() { _overlapFilter.clearIgnoredObjects(); spawnGuide.gameObject.getAllChildrenAndSelf(true, true, _allChildrenAndSeflBuffer); _overlapFilter.setIgnoredObjects(_allChildrenAndSeflBuffer); _overlapFilter.objectTypes = GameObjectType.Mesh | GameObjectType.Sprite; _overlapFilter.ignoredHierarchy = null; } private void skipCellsBeforeSpawn() { float obbInflateAmount = getOverlapCheckOBBInflateAmount(); var overlapConfig = getOverlapCheckConfig(); prepareSceneObjectOverlapFilter(); int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { bool avoidOverlaps = settings.avoidOverlaps; ObjectSpawnCellStack stack = _cellBox.getStack(col, row); if (avoidOverlaps) { OBB stackOBB = stack.obb; stackOBB.inflate(obbInflateAmount); if (stackOBB.isValid && !PluginScene.instance.overlapBox(stackOBB, _overlapFilter, overlapConfig)) avoidOverlaps = false; } int numCells = stack.numCells; for (int cellIndex = 0; cellIndex < numCells; ++cellIndex) { ObjectSpawnCell cell = stack.getCell(cellIndex); if (!cell.isGoodForSpawn) continue; if (Probability.evalChance(settings.objectSkipChance)) { cell.skipped = true; continue; } if (avoidOverlaps) { OBB cellOBB = cell.objectOBB; if (cellOBB.isValid) { cellOBB.inflate(obbInflateAmount); if (PluginScene.instance.overlapBox(cellOBB, _overlapFilter, overlapConfig)) { cell.skipped = true; continue; } } } } } } } private void updateBoxHeight(Vector2Int oldSize) { if (settings.heightMode == BoxObjectSpawnHeightMode.Constant) _cellBox.setHeight(_currentHeight); else if (settings.heightMode == BoxObjectSpawnHeightMode.Random) { // Note: If the old box size is >= than the current size along both axes, keep the old values. if (oldSize.x >= _cellBox.numColumns && oldSize.y >= _cellBox.numRows) return; int numColumns = _cellBox.numColumns; int numRows = _cellBox.numRows; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { if (col >= oldSize.x || row >= oldSize.y) { var stack = _cellBox.getStack(col, row); stack.setHeight(_currentHeight + UnityEngine.Random.Range(settings.minRandomHeight, settings.maxRandomHeight + 1)); } } } } else if (settings.heightMode == BoxObjectSpawnHeightMode.Pattern) { if (_heightPatternDirection == BoxObjectSpawnHeightPatternDirection.LeftToRight || _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.RightToLeft) { int numColumns = _cellBox.numColumns; int numRows = _cellBox.numRows; int startCol = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.LeftToRight ? 0 : numColumns - 1; int endCol = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.LeftToRight ? numColumns - 1 : 0; int colAdd = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.LeftToRight ? 1 : -1; int pastEndCol = endCol + colAdd; for (int row = 0; row < numRows; ++row) { int heightValIndex = 0; for (int col = startCol; col != pastEndCol; col += colAdd) { var stack = _cellBox.getStack(col, row); stack.setHeight(_currentHeight + _heightPatternSampler.sample(_heightPattern, heightValIndex++)); } } } else if (_heightPatternDirection == BoxObjectSpawnHeightPatternDirection.BackToFront || _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.FrontToBack) { int numColumns = _cellBox.numColumns; int numRows = _cellBox.numRows; int startRow = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.FrontToBack ? 0 : numRows - 1; int endRow = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.FrontToBack ? numRows - 1 : 0; int rowAdd = _heightPatternDirection == BoxObjectSpawnHeightPatternDirection.FrontToBack ? 1 : -1; int pastEndRow = endRow + rowAdd; int heightValIndex = 0; for (int row = startRow; row != pastEndRow; row += rowAdd) { int heightVal = _heightPatternSampler.sample(_heightPattern, heightValIndex++); for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); stack.setHeight(_currentHeight + heightVal); } } } } } private bool canApplyCornerGaps() { return _cellBox.numRows > (settings.cornerGapSize * 2) && _cellBox.numColumns > (settings.cornerGapSize * 2); } private void applyCornerMode() { if (settings.cornerMode == BoxObjectSpawnCornerMode.Gap && canApplyCornerGaps()) { int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); stack.setAllCellsSkipped(false); bool colRes = col < settings.cornerGapSize || col >= (numColumns - settings.cornerGapSize); bool rowRes = row < settings.cornerGapSize || row >= (numRows - settings.cornerGapSize); if (colRes && rowRes) stack.setAllCellsSkipped(true); } } } } private void applyFillMode() { if (settings.fillMode == BoxObjectSpawnFillMode.Border) { bool canGapCorners = settings.cornerMode == BoxObjectSpawnCornerMode.Gap && canApplyCornerGaps(); int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); stack.setAllCellsOutOfScope(false); int numCells = stack.numCells; for (int cellIndex = 0; cellIndex < numCells; ++cellIndex) { var cell = stack.getCell(cellIndex); bool cellRes = cellIndex >= settings.borderWidth && cellIndex < (numCells - settings.borderWidth); int actualBorderWidth = settings.borderWidth; if (canGapCorners) actualBorderWidth += settings.cornerGapSize; // Note: Handle special case where the cell sits in the corner. If that is so, // we can skip all other tests. if (canGapCorners) { if (row < settings.cornerGapSize && col == settings.cornerGapSize) continue; if (row < settings.cornerGapSize && col == (numColumns - settings.cornerGapSize - 1)) continue; if (row == settings.cornerGapSize && col <= settings.cornerGapSize) continue; if (row == settings.cornerGapSize && col >= (numColumns - settings.cornerGapSize - 1)) continue; if (row > (numRows - settings.cornerGapSize - 1) && col == settings.cornerGapSize) continue; if (row > (numRows - settings.cornerGapSize - 1) && col == (numColumns - settings.cornerGapSize - 1)) continue; if (row == (numRows - settings.cornerGapSize - 1) && col <= settings.cornerGapSize) continue; if (row == (numRows - settings.cornerGapSize - 1) && col >= (numColumns - settings.cornerGapSize - 1)) continue; } if (col == 0 || col == numColumns - 1) { if (cellRes && row >= actualBorderWidth && row < (numRows - actualBorderWidth)) cell.outOfScope = true; } else if (row == 0 || row == numRows - 1) { if (cellRes && col >= actualBorderWidth && col < (numColumns - actualBorderWidth)) cell.outOfScope = true; } else { if (cellIndex == 0 || cellIndex == numCells - 1) { if ((col >= settings.borderWidth && col < (numColumns - settings.borderWidth)) && (row >= settings.borderWidth && row < (numRows - settings.borderWidth))) cell.outOfScope = true; } else cell.outOfScope = true; } } } } } else if(settings.fillMode == BoxObjectSpawnFillMode.Hollow) { bool canGapCorners = canApplyCornerGaps(); int numRows = _cellBox.numRows; int numColumns = _cellBox.numColumns; for (int row = 0; row < numRows; ++row) { for (int col = 0; col < numColumns; ++col) { var stack = _cellBox.getStack(col, row); stack.setAllCellsOutOfScope(true); bool stackRes; if (settings.cornerMode == BoxObjectSpawnCornerMode.Normal || !canGapCorners) { stackRes = col == 0 || col == numColumns - 1; stackRes |= (row == 0 || row == numRows - 1); } else { if (row < settings.cornerGapSize || row >= (numRows - settings.cornerGapSize)) { stackRes = col == settings.cornerGapSize || col == numColumns - settings.cornerGapSize - 1; stackRes |= (row == 0 || row == numRows - 1); } else { if (row == settings.cornerGapSize || row == (numRows - settings.cornerGapSize - 1)) stackRes = col < settings.cornerGapSize || col >= numColumns - settings.cornerGapSize; else stackRes = col == 0 || col == numColumns - 1; } } int numCells = stack.numCells; for (int cellIndex = 0; cellIndex < numCells; ++cellIndex) { var cell = stack.getCell(cellIndex); if (stackRes || (cellIndex == 0 || cellIndex == numCells - 1)) cell.outOfScope = false; } } } } } private OBB calcRefOBB() { OBB refOBB = calcSpawnGuideWorldOBB(); if (settings.cellMode == BoxObjectSpawnCellMode.Grid) { if (settings.useSceneGridCellSize) refOBB.size = PluginScene.instance.grid.activeSettings.cellSize; else refOBB.size = settings.gridCellSize; } else { if (refOBB.size.magnitude < 1e-5f) refOBB = new OBB(spawnGuide.position, Vector3Ex.create(settings.volumlessObjectSize), spawnGuide.rotation); else { // Note: If the size of the OBB is 0 along one of the extension axes, use the volumeless object size instead. Vector3 obbSize = refOBB.size; float size0 = Vector3Ex.getSizeAlongAxis(obbSize, refOBB.rotation, _extensionPlane.right); float size1 = Vector3Ex.getSizeAlongAxis(obbSize, refOBB.rotation, _extensionPlane.look); if (size0 < 1e-5f || size1 < 1e-5f) { if (obbSize.x < 1e-5f) obbSize.x = settings.volumlessObjectSize; if (obbSize.y < 1e-5f) obbSize.y = settings.volumlessObjectSize; if (obbSize.z < 1e-5f) obbSize.z = settings.volumlessObjectSize; refOBB.size = obbSize; } } } return refOBB; } private void onUndoRedo() { onCancelBoxBuild(); } } } #endif