/* CTexture::duplicatePatch * Duplicates the patch at [index], placing the duplicated patch * at [offset_x],[offset_y] from the original. Returns false if * [index] is out of bounds, true otherwise *******************************************************************/ bool CTexture::duplicatePatch(size_t index, int16_t offset_x, int16_t offset_y) { // Check index if (index >= patches.size()) return false; // Get patch info CTPatch* dp = patches[index]; // Add duplicate patch if (extended) patches.insert(patches.begin() + index, new CTPatchEx((CTPatchEx*)patches[index])); else patches.insert(patches.begin() + index, new CTPatch(patches[index])); // Offset patch by given amount patches[index+1]->setOffsetX(dp->xOffset() + offset_x); patches[index+1]->setOffsetY(dp->yOffset() + offset_y); // Cannot be a simple define anymore this->defined = false; // Announce announce("patches_modified"); return true; }
/* CTextureCanvas::patchAt * Returns the index of the patch at [x,y] on the texture, or -1 * if no patch is at that position *******************************************************************/ int CTextureCanvas::patchAt(int x, int y) { // Check a texture is open if (!texture) return -1; // Go through texture patches backwards (ie from frontmost to back) for (int a = texture->nPatches() - 1; a >= 0; a--) { // Check if x,y is within patch bounds CTPatch* patch = texture->getPatch(a); if (x >= patch->xOffset() && x < patch->xOffset() + (int)patch_textures[a]->getWidth() && y >= patch->yOffset() && y < patch->yOffset() + (int)patch_textures[a]->getHeight()) { return a; } } // No patch at x,y return -1; }
/* CTexture::copyTexture * Copies the texture [tex] to this texture. If keep_type is true, * the current texture type (extended/regular) will be kept, * otherwise it will be converted to the type of [tex] *******************************************************************/ void CTexture::copyTexture(CTexture* tex, bool keep_type) { // Check texture was given if (!tex) return; // Clear current texture clear(); // Copy texture info this->name = tex->name; this->width = tex->width; this->height = tex->height; this->scale_x = tex->scale_x; this->scale_y = tex->scale_y; this->world_panning = tex->world_panning; if (!keep_type) this->extended = tex->extended; this->optional = tex->optional; this->no_decals = tex->no_decals; this->null_texture = tex->null_texture; this->offset_x = tex->offset_x; this->offset_y = tex->offset_y; this->type = tex->type; // Copy patches for (unsigned a = 0; a < tex->nPatches(); a++) { CTPatch* patch = tex->getPatch(a); if (extended) { if (tex->extended) patches.push_back(new CTPatchEx((CTPatchEx*)patch)); else patches.push_back(new CTPatchEx(patch)); } else addPatch(patch->getName(), patch->xOffset(), patch->yOffset()); } }
/* TextureEditorPanel::updatePatchControls * Updates all patch editing controls with values from the currently * selected patch. Behaves differently depending on the number of * patches selected *******************************************************************/ void TextureEditorPanel::updatePatchControls() { // Get selected patches wxArrayInt selection = list_patches->selectedItems(); // If nothing is selected, disable patch controls if (selection.size() == 0) { spin_patch_left->Enable(false); spin_patch_top->Enable(false); } else { // Something is selected, enable the controls spin_patch_left->Enable(true); spin_patch_top->Enable(true); // If only 1 patch is selected, just set the controls to this patch if (selection.size() == 1) { CTPatch* patch = tex_current->getPatch(selection[0]); if (!patch) { wxLogMessage("Error: Selected patch does not exist in texture"); return; } spin_patch_left->SetValue(patch->xOffset()); spin_patch_top->SetValue(patch->yOffset()); } else { // Multiple selection, only enable some controls spin_patch_left->Enable(false); spin_patch_top->Enable(false); } } }
/* TextureEditorPanel::onTexCanvasKeyDown * Called when a key is pressed within the texture canvas *******************************************************************/ void TextureEditorPanel::onTexCanvasKeyDown(wxKeyEvent& e) { // Check if keypress matches any keybinds wxArrayString binds = KeyBind::getBinds(KeyBind::asKeyPress(e.GetKeyCode(), e.GetModifiers())); // Check for alt key if (e.GetKeyCode() == WXK_ALT) alt_press = true; // Go through matching binds int x_movement = 0; int y_movement = 0; bool handled = false; for (unsigned a = 0; a < binds.size(); a++) { string name = binds[a]; // Move patch left if (name == "txed_patch_left") x_movement = -1; else if (name == "txed_patch_left8") x_movement = -8; // Move patch up else if (name == "txed_patch_up") y_movement = -1; else if (name == "txed_patch_up8") y_movement = -8; // Move patch right else if (name == "txed_patch_right") x_movement = 1; else if (name == "txed_patch_right8") x_movement = 8; // Move patch down else if (name == "txed_patch_down") y_movement = 1; else if (name == "txed_patch_down8") y_movement = 8; // Add patch else if (name == "txed_patch_add") { hack_nodrag = true; addPatch(); handled = true; } // Delete patch else if (name == "txed_patch_delete") { removePatch(); handled = true; } // Replace patch else if (name == "txed_patch_replace") { hack_nodrag = true; replacePatch(); handled = true; } // Duplicate patch else if (name == "txed_patch_duplicate") { duplicatePatch(); handled = true; } // Bring patch forward else if (name == "txed_patch_forward") { patchForward(); handled = true; } // Send patch back else if (name == "txed_patch_back") { patchBack(); handled = true; } } // Move patches if needed if (x_movement != 0 || y_movement != 0) { // Do patch duplicate if alt is pressed if (e.GetModifiers() == wxMOD_ALT && alt_press) { duplicatePatch(0, 0); alt_press = false; } wxArrayInt selected_patches = list_patches->selectedItems(); for (size_t a = 0; a < selected_patches.size(); a++) { CTPatch* patch = tex_current->getPatch(selected_patches[a]); if (!patch) continue; int16_t cx = patch->xOffset(); int16_t cy = patch->yOffset(); patch->setOffsetX(cx + x_movement); patch->setOffsetY(cy + y_movement); tex_modified = true; } tex_canvas->redraw(true); handled = true; } if (!e.AltDown()) alt_press = false; if (!handled) e.Skip(); }
/* TextureEditorPanel::onTexCanvasMouseEvent * Called on any mouse event within the texture canvas *******************************************************************/ void TextureEditorPanel::onTexCanvasMouseEvent(wxMouseEvent& e) { // Get mouse position relative to texture point2_t pos = tex_canvas->screenToTexPosition(e.GetX(), e.GetY()); // Get patch that the mouse is over (if any) int patch = tex_canvas->patchAt(pos.x, pos.y); // LEFT DOUBLE CLICK if (e.ButtonDClick(wxMOUSE_BTN_LEFT)) { replacePatch(); } // LEFT MOUSE DOWN else if (e.LeftDown()) { // If not dragging if (e.ShiftDown()) { // Shift is down, add to selection if (patch >= 0) list_patches->selectItem(patch); } else if (e.ControlDown()) { // Control is down, remove from selection if (patch >= 0) list_patches->deSelectItem(patch); } else { // Clear selection only if patch clicked was not already selected if (!tex_canvas->patchSelected(patch)) list_patches->clearSelection(); // Select patch if (patch >= 0) list_patches->selectItem(patch); } } // LEFT MOUSE UP else if (e.LeftUp()) { // Hide texture grid tex_canvas->showGrid(false); // If mouse up over an already-selected patch, and shift/ctrl aren't down, // select only that patch (this mimics 'normal' drag-and-drop/selection behaviour) if (!e.ShiftDown() && !e.ControlDown() && tex_canvas->patchSelected(patch) && !tex_canvas->isDragging()) { list_patches->clearSelection(); list_patches->selectItem(patch); } // Redraw texture canvas tex_canvas->redraw(false); updateTextureControls(); } // RIGHT MOUSE UP else if (e.RightUp()) { // Create context menu wxMenu popup; theApp->getAction("txed_patch_add")->addToMenu(&popup, true); theApp->getAction("txed_patch_remove")->addToMenu(&popup, true); theApp->getAction("txed_patch_replace")->addToMenu(&popup, true); theApp->getAction("txed_patch_back")->addToMenu(&popup, true); theApp->getAction("txed_patch_forward")->addToMenu(&popup, true); theApp->getAction("txed_patch_duplicate")->addToMenu(&popup, true); hack_nodrag = true; PopupMenu(&popup); } // MOUSE DRAGGING else if (e.Dragging()) { // Drag selected patches if left button is down and any patch is selected if (hack_nodrag) hack_nodrag = false; else if (e.LeftIsDown()) { if (list_patches->GetSelectedItemCount() > 0) { // Get drag amount according to texture point2_t tex_cur = tex_canvas->screenToTexPosition(e.GetX(), e.GetY()); point2_t tex_prev = tex_canvas->screenToTexPosition(tex_canvas->getMousePrevPos().x, tex_canvas->getMousePrevPos().y); point2_t diff = tex_cur - tex_prev; // Move any selected patches wxArrayInt selected_patches = list_patches->selectedItems(); for (size_t a = 0; a < selected_patches.size(); a++) { CTPatch* patch = tex_current->getPatch(selected_patches[a]); if (!patch) continue; int16_t cx = patch->xOffset(); int16_t cy = patch->yOffset(); patch->setOffsetX(cx + diff.x); patch->setOffsetY(cy + diff.y); tex_modified = true; } // Refresh texture canvas tex_canvas->showGrid(true); tex_canvas->redraw(false); } else if (tex_current && tex_current->isExtended() && tex_canvas->getViewType() > 0) { // Get drag amount according to texture point2_t tex_cur = tex_canvas->screenToTexPosition(e.GetX(), e.GetY()); point2_t tex_prev = tex_canvas->screenToTexPosition(tex_canvas->getMousePrevPos().x, tex_canvas->getMousePrevPos().y); point2_t diff = tex_cur - tex_prev; // Modify offsets tex_current->setOffsetX(tex_current->getOffsetX() - diff.x); tex_current->setOffsetY(tex_current->getOffsetY() - diff.y); tex_modified = true; // Refresh texture canvas tex_canvas->redraw(false); } } } e.Skip(); }
/* TextureXList::writeTEXTUREXData * Writes the texture list in TEXTUREX format to [texturex], using * [patch_table] for patch information. Returns true on success, * false otherwise *******************************************************************/ bool TextureXList::writeTEXTUREXData(ArchiveEntry* texturex, PatchTable& patch_table) { // Check entry was given if (!texturex) return false; if (texturex->isLocked()) return false; wxLogMessage("Writing " + getTextureXFormatString() + " format TEXTUREx entry"); /* Total size of a TEXTUREx lump, in bytes: Header: 4 + (4 * numtextures) Textures: 22 * numtextures (normal format) 14 * numtextures (nameless format) 18 * numtextures (Strife 1.1 format) Patches: 10 * sum of patchcounts (normal and nameless formats) 6 * sum of patchcounts (Strife 1.1 format) */ size_t numpatchrefs = 0; size_t numtextures = textures.size(); for (size_t i = 0; i < numtextures; ++i) { numpatchrefs += textures[i]->nPatches(); } wxLogMessage("%i patch references in %i textures", numpatchrefs, numtextures); size_t datasize = 0; size_t headersize = 4 + (4 * numtextures); switch (txformat) { case TXF_NORMAL: datasize = 4 + (26 * numtextures) + (10 * numpatchrefs); break; case TXF_NAMELESS: datasize = 4 + (18 * numtextures) + (10 * numpatchrefs); break; case TXF_STRIFE11: datasize = 4 + (22 * numtextures) + ( 6 * numpatchrefs); break; // Some compilers insist on having default cases. default: return false; } MemChunk txdata(datasize); int32_t* offsets = new int32_t[numtextures]; int32_t foo = wxINT32_SWAP_ON_BE((signed) numtextures); // Write header txdata.seek(0, SEEK_SET); SAFEFUNC(txdata.write(&foo, 4)); // Go to beginning of texture definitions SAFEFUNC(txdata.seek(4 + (numtextures*4), SEEK_SET)); // Write texture entries for (size_t i = 0; i < numtextures; ++i) { // Get texture to write CTexture* tex = textures[i]; // Set offset offsets[i] = (signed)txdata.currentPos(); // Write texture entry switch (txformat) { case TXF_NORMAL: { // Create 'normal' doom format texture definition ftdef_t txdef; memset(txdef.name, 0, 8); // Set texture name to all 0's (to ensure compatibility with XWE) strncpy(txdef.name, CHR(tex->getName().Upper()), tex->getName().Len()); txdef.flags = 0; txdef.scale[0] = (tex->getScaleX()*8); txdef.scale[1] = (tex->getScaleY()*8); txdef.width = tex->getWidth(); txdef.height = tex->getHeight(); txdef.columndir[0] = 0; txdef.columndir[1] = 0; txdef.patchcount = tex->nPatches(); // Check for WorldPanning flag if (tex->world_panning) txdef.flags |= TX_WORLDPANNING; // Write texture definition SAFEFUNC(txdata.write(&txdef, 22)); break; } case TXF_NAMELESS: { // Create nameless texture definition nltdef_t txdef; txdef.flags = 0; txdef.scale[0] = (tex->getScaleX()*8); txdef.scale[1] = (tex->getScaleY()*8); txdef.width = tex->getWidth(); txdef.height = tex->getHeight(); txdef.columndir[0] = 0; txdef.columndir[1] = 0; txdef.patchcount = tex->nPatches(); // Write texture definition SAFEFUNC(txdata.write(&txdef, 8)); break; } case TXF_STRIFE11: { // Create strife format texture definition stdef_t txdef; memset(txdef.name, 0, 8); // Set texture name to all 0's (to ensure compatibility with XWE) strncpy(txdef.name, CHR(tex->getName().Upper()), tex->getName().Len()); txdef.flags = 0; txdef.scale[0] = (tex->getScaleX()*8); txdef.scale[1] = (tex->getScaleY()*8); txdef.width = tex->getWidth(); txdef.height = tex->getHeight(); txdef.patchcount = tex->nPatches(); // Check for WorldPanning flag if (tex->world_panning) txdef.flags |= TX_WORLDPANNING; // Write texture definition SAFEFUNC(txdata.write(&txdef, 18)); break; } default: return false; } // Write patch references for (size_t k = 0; k < tex->nPatches(); ++k) { // Get patch to write CTPatch* patch = tex->getPatch(k); // Create patch definition tx_patch_t pdef; pdef.left = patch->xOffset(); pdef.top = patch->yOffset(); // Check for 'invalid' patch if (patch->getName().StartsWith("INVPATCH")) { // Get raw patch index from name string number = patch->getName(); number.Replace("INVPATCH", ""); long index; number.ToLong(&index); pdef.patch = index; } else pdef.patch = patch_table.patchIndex(patch->getName()); // Note this will be -1 if the patch doesn't exist in the patch table. This should never happen with the texture editor, though. // Write common data SAFEFUNC(txdata.write(&pdef, 6)); // In non-Strife formats, there's some added rubbish if (txformat != TXF_STRIFE11) { foo = 0; SAFEFUNC(txdata.write(&foo, 4)); } } } // Write offsets SAFEFUNC(txdata.seek(4, SEEK_SET)); SAFEFUNC(txdata.write(offsets, 4*numtextures)); // Write data to the TEXTUREx entry texturex->importMemChunk(txdata); // Update entry type EntryType::detectEntryType(texturex); // Clean up delete[] offsets; return true; }
/* CTexture::toImage * Generates a SImage representation of this texture, using patches * from [parent] primarily, and the palette [pal] *******************************************************************/ bool CTexture::toImage(SImage& image, Archive* parent, Palette8bit* pal, bool force_rgba) { // Init image image.clear(); image.resize(width, height); // Add patches SImage p_img(PALMASK); si_drawprops_t dp; dp.src_alpha = false; if (defined) { CTPatchEx* patch = (CTPatchEx*)patches[0]; if (!loadPatchImage(0, p_img, parent, pal)) return false; width = p_img.getWidth(); height = p_img.getHeight(); image.resize(width, height); scale_x = (double)width / (double)def_width; scale_y = (double)height / (double)def_height; image.drawImage(p_img, 0, 0, dp, pal, pal); } else if (extended) { // Extended texture // Add each patch to image for (unsigned a = 0; a < patches.size(); a++) { CTPatchEx* patch = (CTPatchEx*)patches[a]; // Load patch entry if (!loadPatchImage(a, p_img, parent, pal)) continue; // Handle offsets int ofs_x = patch->xOffset(); int ofs_y = patch->yOffset(); if (patch->useOffsets()) { ofs_x -= p_img.offset().x; ofs_y -= p_img.offset().y; } // Apply translation before anything in case we're forcing rgba (can't translate rgba images) if (patch->getBlendType() == 1) p_img.applyTranslation(&(patch->getTranslation()), pal); // Convert to RGBA if forced if (force_rgba) p_img.convertRGBA(pal); // Flip/rotate if needed if (patch->flipX()) p_img.mirror(false); if (patch->flipY()) p_img.mirror(true); if (patch->getRotation() != 0) p_img.rotate(patch->getRotation()); // Setup transparency blending dp.blend = NORMAL; dp.alpha = 1.0f; dp.src_alpha = false; if (patch->getStyle() == "CopyAlpha" || patch->getStyle() == "Overlay") dp.src_alpha = true; else if (patch->getStyle() == "Translucent" || patch->getStyle() == "CopyNewAlpha") dp.alpha = patch->getAlpha(); else if (patch->getStyle() == "Add") { dp.blend = ADD; dp.alpha = patch->getAlpha(); } else if (patch->getStyle() == "Subtract") { dp.blend = SUBTRACT; dp.alpha = patch->getAlpha(); } else if (patch->getStyle() == "ReverseSubtract") { dp.blend = REVERSE_SUBTRACT; dp.alpha = patch->getAlpha(); } else if (patch->getStyle() == "Modulate") { dp.blend = MODULATE; dp.alpha = patch->getAlpha(); } // Setup patch colour if (patch->getBlendType() == 2) p_img.colourise(patch->getColour(), pal); else if (patch->getBlendType() == 3) p_img.tint(patch->getColour(), patch->getColour().fa(), pal); // Add patch to texture image image.drawImage(p_img, ofs_x, ofs_y, dp, pal, pal); } } else { // Normal texture // Add each patch to image for (unsigned a = 0; a < patches.size(); a++) { CTPatch* patch = patches[a]; if (Misc::loadImageFromEntry(&p_img, patch->getPatchEntry(parent))) image.drawImage(p_img, patch->xOffset(), patch->yOffset(), dp, pal, pal); } } return true; }
/* CTextureCanvas::drawPatch * Draws the patch at index [num] in the composite texture *******************************************************************/ void CTextureCanvas::drawPatch(int num, bool outside) { // Get patch to draw CTPatch* patch = texture->getPatch(num); // Check it exists if (!patch) return; // Load the patch as an opengl texture if it isn't already if (!patch_textures[num]->isLoaded()) { SImage temp(PALMASK); if (texture->loadPatchImage(num, temp, parent, &palette)) { // Load the image as a texture patch_textures[num]->loadImage(&temp, &palette); } else patch_textures[num]->genChequeredTexture(8, COL_RED, COL_BLACK); } // Translate to position glPushMatrix(); glTranslated(patch->xOffset(), patch->yOffset(), 0); // Setup rendering options glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Setup extended features bool flipx = false; bool flipy = false; double alpha = 1.0; bool shade_select = true; rgba_t col = COL_WHITE; if (texture->isExtended()) { // Get extended patch CTPatchEx* epatch = (CTPatchEx*)patch; // Flips if (epatch->flipX()) flipx = true; if (epatch->flipY()) flipy = true; // Rotation if (epatch->getRotation() == 90) { glTranslated(patch_textures[num]->getHeight(), 0, 0); glRotated(90, 0, 0, 1); } else if (epatch->getRotation() == 180) { glTranslated(patch_textures[num]->getWidth(), patch_textures[num]->getHeight(), 0); glRotated(180, 0, 0, 1); } else if (epatch->getRotation() == -90) { glTranslated(0, patch_textures[num]->getWidth(), 0); glRotated(-90, 0, 0, 1); } } // Set colour if (outside) glColor4f(0.8f, 0.2f, 0.2f, 0.3f); else glColor4f(col.fr(), col.fg(), col.fb(), alpha); // Draw the patch patch_textures[num]->draw2d(0, 0, flipx, flipy); glPopMatrix(); }
/* CTextureCanvas::drawTexture * Draws the currently opened composite texture *******************************************************************/ void CTextureCanvas::drawTexture() { // Push matrix glPushMatrix(); // Calculate top-left position of texture (for glScissor, since it ignores the current translation/scale) double left = offset.x + (GetSize().x * 0.5) - (texture->getWidth() * 0.5 * scale); double top = -offset.y + (GetSize().y * 0.5) - (texture->getHeight() * 0.5 * scale); // Translate to middle of the canvas glTranslated(GetSize().x * 0.5, GetSize().y * 0.5, 0); // Zoom double yscale = (tx_arc ? scale * 1.2 : scale); glScaled(scale, yscale, 1); // Draw offset guides if needed drawOffsetLines(); // Apply texture scale double tscalex = 1; double tscaley = 1; if (tex_scale) { tscalex = texture->getScaleX(); if (tscalex == 0) tscalex = 1; tscaley = texture->getScaleY(); if (tscaley == 0) tscaley = 1; glScaled(1.0 / tscalex, 1.0 / tscaley, 1); } // Translate by offsets if needed if (view_type == 0) glTranslated(texture->getWidth() * -0.5, texture->getHeight() * -0.5, 0); // No offsets if (view_type >= 1) glTranslated(-texture->getOffsetX(), -texture->getOffsetY(), 0); // Sprite offsets if (view_type == 2) glTranslated(-160*tscalex, -100*tscaley, 0); // HUD offsets // Draw the texture border //if (gfx_show_border) drawTextureBorder(); // Enable textures glEnable(GL_TEXTURE_2D); // First, draw patches semitransparently (for anything outside the texture) // But only if we are drawing stuff outside the texture area if (draw_outside) { for (uint32_t a = 0; a < texture->nPatches(); a++) drawPatch(a, true); } // Reset colouring OpenGL::setColour(COL_WHITE); // If we're currently dragging, draw a 'basic' preview of the texture using opengl if (dragging) { glEnable(GL_SCISSOR_TEST); glScissor(left, top, texture->getWidth() * scale, texture->getHeight() * scale); for (uint32_t a = 0; a < texture->nPatches(); a++) drawPatch(a); glDisable(GL_SCISSOR_TEST); } // Otherwise, draw the fully generated texture else { // Generate if needed if (!tex_preview.isLoaded()) { // Determine image type SIType type = PALMASK; if (blend_rgba) type = RGBA; // CTexture -> temp Image -> GLTexture SImage temp(type); texture->toImage(temp, parent, &palette, blend_rgba); tex_preview.loadImage(&temp, &palette); } // Draw it tex_preview.draw2d(); } // Disable textures glDisable(GL_TEXTURE_2D); // Now loop through selected patches and draw selection outlines OpenGL::setColour(70, 210, 220, 255, BLEND_NORMAL); glEnable(GL_LINE_SMOOTH); glLineWidth(1.5f); for (size_t a = 0; a < selected_patches.size(); a++) { // Skip if not selected if (!selected_patches[a]) continue; // Get patch CTPatch* patch = texture->getPatch(a); CTPatchEx* epatch = (CTPatchEx*)patch; // Check for rotation if (texture->isExtended() && (epatch->getRotation() == 90 || epatch->getRotation() == -90)) { // Draw outline, width/height swapped glBegin(GL_LINE_LOOP); glVertex2i(patch->xOffset(), patch->yOffset()); glVertex2i(patch->xOffset(), patch->yOffset() + (int)patch_textures[a]->getWidth()); glVertex2i(patch->xOffset() + (int)patch_textures[a]->getHeight(), patch->yOffset() + (int)patch_textures[a]->getWidth()); glVertex2i(patch->xOffset() + (int)patch_textures[a]->getHeight(), patch->yOffset()); glEnd(); } else { // Draw outline glBegin(GL_LINE_LOOP); glVertex2i(patch->xOffset(), patch->yOffset()); glVertex2i(patch->xOffset(), patch->yOffset() + (int)patch_textures[a]->getHeight()); glVertex2i(patch->xOffset() + (int)patch_textures[a]->getWidth(), patch->yOffset() + (int)patch_textures[a]->getHeight()); glVertex2i(patch->xOffset() + (int)patch_textures[a]->getWidth(), patch->yOffset()); glEnd(); } } // Finally, draw a hilight outline if anything is hilighted if (hilight_patch >= 0) { // Set colour OpenGL::setColour(255, 255, 255, 150, BLEND_ADDITIVE); // Get patch CTPatch* patch = texture->getPatch(hilight_patch); CTPatchEx* epatch = (CTPatchEx*)patch; GLTexture* patch_texture = patch_textures[hilight_patch]; // Check for rotation if (texture->isExtended() && (epatch->getRotation() == 90 || epatch->getRotation() == -90)) { // Draw outline, width/height swapped glBegin(GL_LINE_LOOP); glVertex2i(patch->xOffset(), patch->yOffset()); glVertex2i(patch->xOffset(), patch->yOffset() + (int)patch_texture->getWidth()); glVertex2i(patch->xOffset() + (int)patch_texture->getHeight(), patch->yOffset() + (int)patch_texture->getWidth()); glVertex2i(patch->xOffset() + (int)patch_texture->getHeight(), patch->yOffset()); glEnd(); } else { // Draw outline glBegin(GL_LINE_LOOP); glVertex2i(patch->xOffset(), patch->yOffset()); glVertex2i(patch->xOffset(), patch->yOffset() + (int)patch_texture->getHeight()); glVertex2i(patch->xOffset() + (int)patch_texture->getWidth(), patch->yOffset() + (int)patch_texture->getHeight()); glVertex2i(patch->xOffset() + (int)patch_texture->getWidth(), patch->yOffset()); glEnd(); } } glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); // Pop matrix glPopMatrix(); }