コード例 #1
0
ファイル: CSG.cpp プロジェクト: BielBdeLuna/DarkRadiant
void mergeSelectedBrushes(const cmd::ArgumentList& args)
{
	// Get the current selection
	BrushPtrVector brushes = selection::algorithm::getSelectedBrushes();

	if (brushes.empty()) {
		rMessage() << _("CSG Merge: No brushes selected.") << std::endl;
		wxutil::Messagebox::ShowError(_("CSG Merge: No brushes selected."));
		return;
	}

	if (brushes.size() < 2) {
		rMessage() << "CSG Merge: At least two brushes have to be selected.\n";
		wxutil::Messagebox::ShowError("CSG Merge: At least two brushes have to be selected.");
		return;
	}

	rMessage() << "CSG Merge: Merging " << brushes.size() << " brushes." << std::endl;

	UndoableCommand undo("mergeSelectedBrushes");

	// Take the last selected node as reference for layers and parent
	scene::INodePtr merged = GlobalSelectionSystem().ultimateSelected();

	scene::INodePtr parent = merged->getParent();
	assert(parent != NULL);

	// Create a new BrushNode
	scene::INodePtr node = GlobalBrushCreator().createBrush();

	// Insert the newly created brush into the (same) parent entity
	parent->addChildNode(node);

	// Move the new brush to the same layers as the merged one
	node->assignToLayers(merged->getLayers());

	// Get the contained brush
	Brush* brush = Node_getBrush(node);

	// Attempt to merge the selected brushes into the new one
	if (!Brush_merge(*brush, brushes, true))
	{
		rWarning() << "CSG Merge: Failed - result would not be convex." << std::endl;
		return;
	}

	ASSERT_MESSAGE(!brush->empty(), "brush left with no faces after merge");

	// Remove the original brushes
	for (BrushPtrVector::iterator i = brushes.begin(); i != brushes.end(); ++i)
	{
		scene::removeNodeFromParent(*i);
	}

	// Select the new brush
	Node_setSelected(node, true);

	rMessage() << "CSG Merge: Succeeded." << std::endl;
	SceneChangeNotify();
}
コード例 #2
0
void GroupCycle::updateSelection() {
	_updateActive = true;

	// Do some sanity checking before we run into crashes
	if (_index >= 0 && _index < static_cast<int>(_list.size())) {
		for (std::size_t i = 0; i < _list.size(); i++) {
			Node_setSelected(_list[i], false);
		}

		Node_setSelected(_list[_index], true);
	}

	SceneChangeNotify();

	_updateActive = false;
}
コード例 #3
0
void SelectionSet::deselect()
{
    for (NodeSet::iterator i = _nodes.begin(); i != _nodes.end(); ++i)
    {
        scene::INodePtr node = i->lock();

        if (node == NULL) continue; // skip deleted nodes

        if (!node->visible()) continue; // skip invisible, non-instantiated nodes

        Node_setSelected(node, false);
    }
}
コード例 #4
0
ファイル: CSG.cpp プロジェクト: BielBdeLuna/DarkRadiant
void hollowBrush(const BrushNodePtr& sourceBrush, bool makeRoom)
{
	// Hollow the brush using the current grid size
    sourceBrush->getBrush().forEachFace([&] (Face& face)
    {
        if (!face.contributes())
        {
            return;
        }

        scene::INodePtr parent = sourceBrush->getParent();

        scene::INodePtr newNode = GlobalBrushCreator().createBrush();
        BrushNodePtr brushNode = std::dynamic_pointer_cast<BrushNode>(newNode);
        assert(brushNode);

        // Add the child to the same parent as the source brush
        parent->addChildNode(brushNode);

        // Move the child brushes to the same layer as their source
        brushNode->assignToLayers(sourceBrush->getLayers());

        // Copy all faces from the source brush
        brushNode->getBrush().copy(sourceBrush->getBrush());

        Node_setSelected(brushNode, true);

        FacePtr newFace = brushNode->getBrush().addFace(face);

        if (newFace != 0)
        {
            float offset = GlobalGrid().getGridSize();

            newFace->flipWinding();
            newFace->getPlane().offset(offset);
            newFace->planeChanged();

            if (makeRoom)
            {
                // Retrieve the normal vector of the "source" face
                brushNode->getBrush().transform(Matrix4::getTranslation(face.getPlane().getPlane().normal() * offset));
                brushNode->getBrush().freezeTransform();
            }
        }

        brushNode->getBrush().removeEmptyFaces();
    });

	// Now unselect and remove the source brush from the scene
	scene::removeNodeFromParent(sourceBrush);
}
コード例 #5
0
	// Adds the cloned nodes to their designated parents. Pass TRUE to select the nodes.
	void moveClonedNodes(bool select) {
		for (Map::iterator i = _cloned.begin(); i != _cloned.end(); ++i)
		{
			// Remove the child from the basic container first
			_cloneRoot->removeChildNode(i->first);

			// Add the node to its parent
			i->second->addChildNode(i->first);

			if (select) {
				Node_setSelected(i->first, true);
			}
		}
	}
コード例 #6
0
void SelectBrush (int entitynum, int brushnum)
{
  scene::Path path;
  Scene_FindEntityBrush(entitynum, brushnum, path);
  if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
  {
	  Node_setSelected(path.top(), true);
    
    XYWndPtr xyView = GlobalXYWnd().getActiveXY();
    
    if (xyView) {
    	xyView->positionView(path.top()->worldAABB().origin);
    }
  }
}
コード例 #7
0
	bool pre(const scene::INodePtr& node) {
		if (node->visible()) {
			Brush* brush = Node_getBrush(node);

			if (brush != NULL && brush->hasShader(_name)) {
				Node_setSelected(node, true);
				return false; // don't traverse brushes
			}

			// not a suitable brush, traverse further
			return true;
		}
		else {
			return false; // don't traverse invisible nodes
		}
	}
コード例 #8
0
ファイル: Prefab.cpp プロジェクト: OpenTechEngine/DarkRadiant
void constructPrefab(const AABB& aabb, const std::string& shader, EPatchPrefab eType, 
					 EViewType viewType, std::size_t width, std::size_t height)
{
	GlobalSelectionSystem().setSelectedAll(false);

	scene::INodePtr node(GlobalPatchCreator(DEF2).createPatch());
	GlobalMap().findOrInsertWorldspawn()->addChildNode(node);

	Patch* patch = Node_getPatch(node);
	patch->setShader(shader);

	patch->ConstructPrefab(aabb, eType, viewType, width, height);
	patch->controlPointsChanged();

	Node_setSelected(node, true);
}
コード例 #9
0
ファイル: Curves.cpp プロジェクト: BielBdeLuna/DarkRadiant
/** 
 * greebo: Creates a new entity with an attached curve
 *
 * @key: The curve type: pass either "curve_CatmullRomSpline" or "curve_Nurbs".
 */
void createCurve(const std::string& key)
{
    UndoableCommand undo(std::string("createCurve: ") + key);

    // De-select everything before we proceed
    GlobalSelectionSystem().setSelectedAll(false);
    GlobalSelectionSystem().setSelectedAllComponents(false);

    std::string curveEClass = game::current::getValue<std::string>(GKEY_DEFAULT_CURVE_ENTITY);

    // Fallback to func_static, if nothing defined in the registry
    if (curveEClass.empty()) {
        curveEClass = "func_static";
    }

    // Find the default curve entity
    IEntityClassPtr entityClass = GlobalEntityClassManager().findOrInsert(
        curveEClass,
        true
    );

    // Create a new entity node deriving from this entityclass
    IEntityNodePtr curve(GlobalEntityCreator().createEntity(entityClass));

    // Insert this new node into the scenegraph root
    GlobalSceneGraph().root()->addChildNode(curve);

    // Select this new curve node
    Node_setSelected(curve, true);

    // Set the model key to be the same as the name
    curve->getEntity().setKeyValue("model",
                                   curve->getEntity().getKeyValue("name"));

    // Initialise the curve using three pre-defined points
    curve->getEntity().setKeyValue(
        key,
        "3 ( 0 0 0  50 50 0  50 100 0 )"
    );

    ITransformablePtr transformable = Node_getTransformable(curve);
    if (transformable != NULL) {
        // Translate the entity to the center of the current workzone
        transformable->setTranslation(GlobalXYWnd().getActiveXY()->getOrigin());
        transformable->freezeTransform();
    }
}
コード例 #10
0
BrushByPlaneClipper::~BrushByPlaneClipper() {
	for (std::set<scene::INodePtr>::iterator i = _deleteList.begin(); 
		 i != _deleteList.end(); i++)
	{
		// Remove the node from the scene
		scene::removeNodeFromParent(*i);
	}

	for (InsertMap::iterator i = _insertList.begin();
		 i != _insertList.end(); i++)
	{
		// Insert the child into the designated parent
		scene::addNodeToContainer(i->first, i->second);

		// Select the child
		Node_setSelected(i->first, true);
	}
}
コード例 #11
0
bool EntitySelectByClassnameWalker::pre(const scene::INodePtr& node) {
	// don't traverse invisible nodes
	if (!node->visible()) return false; 

	Entity* entity = Node_getEntity(node);
	
	if (entity != NULL) {

		if (entityMatches(entity)) {
			// Got a matching entity
			Node_setSelected(node, true);
		}

		// Don't traverse entities
		return false;
	}

	// Not an entity, traverse
	return true;
}
コード例 #12
0
void BrushByPlaneClipper::split(const BrushPtrVector& brushes)
{
	Plane3 plane(_p0, _p1, _p2);

	if (!plane.isValid())
	{
		return;
	}

	for (BrushPtrVector::const_iterator i = brushes.begin(); i != brushes.end(); ++i)
	{
		const BrushNodePtr& node = *i;

		// Don't clip invisible nodes
		if (!node->visible())
		{
			continue;
		}

		Brush& brush = node->getBrush();

		scene::INodePtr parent = node->getParent();

		if (!parent)
		{
			continue;
		}

		// greebo: Analyse the brush to find out which shader is the most used one
		getMostUsedTexturing(brush);

		BrushSplitType split = Brush_classifyPlane(brush, _split == eFront ? -plane : plane);

		if (split.counts[ePlaneBack] && split.counts[ePlaneFront])
		{
			// the plane intersects this brush
			if (_split == eFrontAndBack)
			{
				scene::INodePtr fragmentNode = GlobalBrushCreator().createBrush();

				assert(fragmentNode != NULL);

				// Put the fragment in the same layer as the brush it was clipped from
				// Do this before adding the fragment to the parent, since there is an
				// update algorithm setting the visibility of the fragment right there.
				scene::assignNodeToLayers(fragmentNode, node->getLayers());

				// greebo: For copying the texture scale the new node needs to be inserted in the scene
				// otherwise the shaders cannot be captured and the scale is off

				// Insert the child into the designated parent
				scene::addNodeToContainer(fragmentNode, parent);

				// Select the child
				Node_setSelected(fragmentNode, true);

				Brush* fragment = Node_getBrush(fragmentNode);
				assert(fragment != NULL);
				fragment->copy(brush);

				FacePtr newFace = fragment->addPlane(_p0, _p1, _p2, _mostUsedShader, _mostUsedProjection);

				if (newFace != NULL && _split != eFront)
				{
					newFace->flipWinding();
				}

				fragment->removeEmptyFaces();
				ASSERT_MESSAGE(!fragment->empty(), "brush left with no faces after split");
			}

			FacePtr newFace = brush.addPlane(_p0, _p1, _p2, _mostUsedShader, _mostUsedProjection);

			if (newFace != NULL && _split == eFront) {
				newFace->flipWinding();
			}

			brush.removeEmptyFaces();
			ASSERT_MESSAGE(!brush.empty(), "brush left with no faces after split");
		}
		// the plane does not intersect this brush
		else if (_split != eFrontAndBack && split.counts[ePlaneBack] != 0)
		{
			// the brush is "behind" the plane
			// Remove the node from the scene
			scene::removeNodeFromParent(node);
		}
	}
}
コード例 #13
0
ファイル: Prefab.cpp プロジェクト: OpenTechEngine/DarkRadiant
void createCaps(Patch& patch, const scene::INodePtr& parent, EPatchCap type, const std::string& shader)
{
	if ((type == eCapEndCap || type == eCapIEndCap) && patch.getWidth() != 5)
	{
		rError() << "cannot create end-cap - patch width != 5" << std::endl;

		gtkutil::MessageBox::ShowError(_("Cannot create end-cap, patch must have a width of 5."),
			GlobalMainFrame().getTopLevelWindow());

		return;
	}

	if ((type == eCapBevel || type == eCapIBevel) && patch.getWidth() != 3)
	{
		gtkutil::MessageBox::ShowError(_("Cannot create bevel-cap, patch must have a width of 3."),
			GlobalMainFrame().getTopLevelWindow());

		rError() << "cannot create bevel-cap - patch width != 3" << std::endl;
		return;

	}

	if (type == eCapCylinder && patch.getWidth() != 9)
	{
		gtkutil::MessageBox::ShowError(_("Cannot create cylinder-cap, patch must have a width of 9."),
			GlobalMainFrame().getTopLevelWindow());

		rError() << "cannot create cylinder-cap - patch width != 9" << std::endl;
		return;
	}

	assert(parent != NULL);

	{
		scene::INodePtr cap(GlobalPatchCreator(DEF2).createPatch());
		parent->addChildNode(cap);

		Patch* capPatch = Node_getPatch(cap);
		assert(capPatch != NULL);

		patch.MakeCap(capPatch, type, ROW, true);
		capPatch->setShader(shader);

		// greebo: Avoid creating "degenerate" patches (all vertices merged in one 3D point)
		if (!capPatch->isDegenerate())
		{
			Node_setSelected(cap, true);
		}
		else
		{
			parent->removeChildNode(cap);
			rWarning() << "Prevented insertion of degenerate patch." << std::endl;
		}
	}

	{
		scene::INodePtr cap(GlobalPatchCreator(DEF2).createPatch());
		parent->addChildNode(cap);

		Patch* capPatch = Node_getPatch(cap);
		assert(capPatch != NULL);

		patch.MakeCap(capPatch, type, ROW, false);
		capPatch->setShader(shader);

		// greebo: Avoid creating "degenerate" patches (all vertices merged in one 3D point)
		if (!capPatch->isDegenerate())
		{
			Node_setSelected(cap, true);
		}
		else
		{
			parent->removeChildNode(cap);
			rWarning() << "Prevented insertion of degenerate patch." << std::endl;
		}
	}
}
コード例 #14
0
// Try to create a CM from the selected entity
void createCMFromSelection(const cmd::ArgumentList& args) {
	// Check the current selection state
	const SelectionInfo& info = GlobalSelectionSystem().getSelectionInfo();
	
	if (info.totalCount == info.entityCount && info.totalCount == 1) {
		// Retrieve the node, instance and entity
		const scene::INodePtr& entityNode = GlobalSelectionSystem().ultimateSelected();
		
		// Try to retrieve the group node
		scene::GroupNodePtr groupNode = Node_getGroupNode(entityNode);
		
		// Remove the entity origin from the brushes
		if (groupNode != NULL) {
			groupNode->removeOriginFromChildren();
			
			// Deselect the node
			Node_setSelected(entityNode, false);
			
			// Select all the child nodes
			NodeSelector visitor;
			entityNode->traverse(visitor);
			
			BrushPtrVector brushes = algorithm::getSelectedBrushes();
		
			// Create a new collisionmodel on the heap using a shared_ptr
			cmutil::CollisionModelPtr cm(new cmutil::CollisionModel());
		
			// Add all the brushes to the collision model
			for (std::size_t i = 0; i < brushes.size(); i++) {
				cm->addBrush(brushes[i]->getBrush());
			}
			
			ui::ModelSelectorResult modelAndSkin = ui::ModelSelector::chooseModel("", false, false);
			std::string basePath = GlobalGameManager().getModPath();
			
			std::string modelPath = basePath + modelAndSkin.model;
			
			std::string newExtension = "." + GlobalRegistry().get(RKEY_CM_EXT);
			
			// Set the model string to correctly associate the clipmodel
			cm->setModel(modelAndSkin.model);
			
			try {
				// create the new autosave filename by changing the extension
				Path cmPath = boost::filesystem::change_extension(
						Path(modelPath, boost::filesystem::native), 
						newExtension
					);
				
				// Open the stream to the output file
				std::ofstream outfile(cmPath.string().c_str());
				
				if (outfile.is_open()) {
					// Insert the CollisionModel into the stream
					outfile << *cm;
					// Close the file
					outfile.close();
					
					globalOutputStream() << "CollisionModel saved to " << cmPath.string() << std::endl;
				}
				else {
					gtkutil::errorDialog(
						(boost::format("Couldn't save to file: %s") % cmPath.string()).str(),
						 GlobalMainFrame().getTopLevelWindow());
				}
			}
			catch (boost::filesystem::filesystem_error f) {
				globalErrorStream() << "CollisionModel: " << f.what() << std::endl;
			}
			
			// De-select the child brushes
			GlobalSelectionSystem().setSelectedAll(false);
			
			// Re-add the origin to the brushes
			groupNode->addOriginToChildren();
		
			// Re-select the node
			Node_setSelected(entityNode, true);
		}
	}
	else {
		gtkutil::errorDialog(
			_(ERRSTR_WRONG_SELECTION.c_str()), 
			GlobalMainFrame().getTopLevelWindow());
	}
}
コード例 #15
0
ファイル: General.cpp プロジェクト: Zbyl/DarkRadiant
void thicken(const PatchNodePtr& sourcePatch, float thickness, bool createSeams, int axis)
{
	// Get a shortcut to the patchcreator
	PatchCreator& patchCreator = GlobalPatchCreator(DEF2);

	// Create a new patch node
	scene::INodePtr node(patchCreator.createPatch());

	scene::INodePtr parent = sourcePatch->getParent();
	assert(parent != NULL);

	// Insert the node into the same parent as the existing patch
	parent->addChildNode(node);

	// Retrieve the contained patch from the node
	Patch* targetPatch = Node_getPatch(node);

	// Create the opposite patch with the given thickness = distance
	targetPatch->createThickenedOpposite(sourcePatch->getPatchInternal(), thickness, axis);

	// Select the newly created patch
	Node_setSelected(node, true);

	if (createSeams && thickness > 0.0f)
	{
		// Allocate four new patches
		scene::INodePtr nodes[4] = {
			patchCreator.createPatch(),
			patchCreator.createPatch(),
			patchCreator.createPatch(),
			patchCreator.createPatch()
		};

		// Now create the four walls
		for (int i = 0; i < 4; i++)
		{
			// Insert each node into the same parent as the existing patch
			// It's vital to do this first, otherwise these patches won't have valid shaders
			parent->addChildNode(nodes[i]);

			// Retrieve the contained patch from the node
			Patch* wallPatch = Node_getPatch(nodes[i]);

			// Create the wall patch by passing i as wallIndex
			wallPatch->createThickenedWall(sourcePatch->getPatchInternal(), *targetPatch, i);

			if (!wallPatch->isDegenerate())
			{
				// Now select the newly created patch
				Node_setSelected(nodes[i], true);
			}
			else
			{
				rMessage() << "Thicken: Discarding degenerate patch." << std::endl;

				// Remove again
				parent->removeChildNode(nodes[i]);
			}
		}
	}

	// Invert the target patch so that it faces the opposite direction
	targetPatch->InvertMatrix();
}
コード例 #16
0
/** greebo: Tries to select the given node.
 */
void selectNode(scene::INodePtr node) {
	Node_setSelected(node, true);
}
コード例 #17
0
ファイル: entity.cpp プロジェクト: DerSaidin/DarkRadiant
/**
 * Create an instance of the given entity at the given position, and return
 * the Node containing the new entity.
 *
 * @returns: the scene::INodePtr referring to the new entity.
 */
scene::INodePtr createEntityFromSelection(const std::string& name, const Vector3& origin)
{
    // Obtain the structure containing the selection counts
    const SelectionInfo& info = GlobalSelectionSystem().getSelectionInfo();

    IEntityClassPtr entityClass = GlobalEntityClassManager().findOrInsert(name, true);

    // TODO: to be replaced by inheritance-based class detection
    bool isModel = (info.totalCount == 0 && name == "func_static");

    // Some entities are based on the size of the currently-selected primitive(s)
    bool primitivesSelected = info.brushCount > 0 || info.patchCount > 0;

    if (!(entityClass->isFixedSize() || isModel) && !primitivesSelected) {
        throw EntityCreationException(
            (boost::format(_("Unable to create entity %s, no brushes selected.")) % name).str()
        );
    }

    // Get the selection workzone bounds
    AABB workzone = GlobalSelectionSystem().getWorkZone().bounds;

    // Create the new node for the entity
    IEntityNodePtr node(GlobalEntityCreator().createEntity(entityClass));

    GlobalSceneGraph().root()->addChildNode(node);

    // The layer list we're moving the newly created node/subgraph into
    scene::LayerList targetLayers;

    if (entityClass->isFixedSize() || (isModel && !primitivesSelected))
    {
        selection::algorithm::deleteSelection();

        ITransformablePtr transform = Node_getTransformable(node);

        if (transform != 0) {
            transform->setType(TRANSFORM_PRIMITIVE);
            transform->setTranslation(origin);
            transform->freezeTransform();
        }

        GlobalSelectionSystem().setSelectedAll(false);

        // Move the item to the active layer
        targetLayers.insert(GlobalLayerSystem().getActiveLayer());

        Node_setSelected(node, true);
    }
    else // brush-based entity
    {
        // Add selected brushes as children of non-fixed entity
        node->getEntity().setKeyValue("model",
                                      node->getEntity().getKeyValue("name"));

        // Take the selection center as new origin
        Vector3 newOrigin = selection::algorithm::getCurrentSelectionCenter();
        node->getEntity().setKeyValue("origin", string::to_string(newOrigin));

        // If there is an "editor_material" class attribute, apply this shader
        // to all of the selected primitives before parenting them
        std::string material = node->getEntity().getEntityClass()->getAttribute("editor_material").getValue();

        if (!material.empty()) {
            selection::algorithm::applyShaderToSelection(material);
        }

        // If we had primitives to reparent, the new entity should inherit the layer info from them
        if (primitivesSelected)
        {
            scene::INodePtr primitive = GlobalSelectionSystem().ultimateSelected();
            targetLayers = primitive->getLayers();
        }
        else
        {
            // Otherwise move the item to the active layer
            targetLayers.insert(GlobalLayerSystem().getActiveLayer());
        }

        // Parent the selected primitives to the new node
        selection::algorithm::ParentPrimitivesToEntityWalker walker(node);
        GlobalSelectionSystem().foreachSelected(walker);
        walker.reparent();

        // De-select the children and select the newly created parent entity
        GlobalSelectionSystem().setSelectedAll(false);
        Node_setSelected(node, true);
    }

    // Assign the layers - including all child nodes (#2864)
    scene::AssignNodeToLayersWalker layerWalker(targetLayers);
    Node_traverseSubgraph(node, layerWalker);

    // Set the light radius and origin

    if (entityClass->isLight() && primitivesSelected)
    {
        AABB bounds(Doom3Light_getBounds(workzone));
        node->getEntity().setKeyValue("origin",
                                      string::to_string(bounds.getOrigin()));
        node->getEntity().setKeyValue("light_radius",
                                      string::to_string(bounds.getExtents()));
    }

    // Flag the map as unsaved after creating the entity
    GlobalMap().setModified(true);

    // Check for auto-setting key values. TODO: use forEachClassAttribute
    // directly here.
    eclass::AttributeList list = eclass::getSpawnargsWithPrefix(
        *entityClass, "editor_setKeyValue"
    );

    if (!list.empty())
    {
        for (eclass::AttributeList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
            // Cut off the "editor_setKeyValueN " string from the key to get the spawnarg name
            std::string key = i->getName().substr(i->getName().find_first_of(' ') + 1);
            node->getEntity().setKeyValue(key, i->getValue());
        }
    }

    // Return the new node
    return node;
}
コード例 #18
0
	void createDecals() {
		for (FaceInstanceList::iterator i = _faceInstances.begin(); i != _faceInstances.end(); ++i) {
			// Get the winding
			const Winding& winding = (*i)->getFace().getWinding();

			// Create a new decal patch
			scene::INodePtr patchNode = GlobalPatchCreator(DEF3).createPatch();
			
			if (patchNode == NULL) {
				gtkutil::errorDialog(_("Could not create patch."), GlobalMainFrame().getTopLevelWindow());
				return;
			}
			
			Patch* patch = Node_getPatch(patchNode);
			assert(patch != NULL); // must not fail
			
			// Set the tesselation of that 3x3 patch
			patch->setDims(3,3);
			patch->setFixedSubdivisions(true, Subdivisions(1,1));
			
			// Set the coordinates
			patch->ctrlAt(0,0).vertex = winding[0].vertex;
			patch->ctrlAt(2,0).vertex = winding[1].vertex;
			patch->ctrlAt(1,0).vertex = (patch->ctrlAt(0,0).vertex + patch->ctrlAt(2,0).vertex)/2;
			
			patch->ctrlAt(0,1).vertex = (winding[0].vertex + winding[3].vertex)/2;
			patch->ctrlAt(2,1).vertex = (winding[1].vertex + winding[2].vertex)/2;
			
			patch->ctrlAt(1,1).vertex = (patch->ctrlAt(0,1).vertex + patch->ctrlAt(2,1).vertex)/2;
			
			patch->ctrlAt(2,2).vertex = winding[2].vertex;
			patch->ctrlAt(0,2).vertex = winding[3].vertex;
			patch->ctrlAt(1,2).vertex = (patch->ctrlAt(2,2).vertex + patch->ctrlAt(0,2).vertex)/2;

			// Use the texture in the clipboard, if it's a decal texture
			Texturable& clipboard = GlobalShaderClipboard().getSource();

			if (!clipboard.empty())
			{
				if (clipboard.getShader().find("decals") != std::string::npos)
				{
					patch->setShader(clipboard.getShader());
				}
			}

			// Fit the texture on it
			patch->SetTextureRepeat(1,1);
			patch->FlipTexture(1);
			
			// Insert the patch into worldspawn
			scene::INodePtr worldSpawnNode = GlobalMap().getWorldspawn();
			assert(worldSpawnNode != NULL); // This must be non-NULL, otherwise we won't have faces
			
			worldSpawnNode->addChildNode(patchNode);

			// Deselect the face instance
			(*i)->setSelected(SelectionSystem::eFace, false);

			// Select the patch
			Node_setSelected(patchNode, true);
		}
	}