feat(render): 实现网格液化变形功能
- 添加向量变换工具方法,支持旋转和缩放变换 - 实现网格顶点的动态增删改功能 - 添加液化状态可视化渲染,包括顶点显示和状态指示器 - 支持创建细分网格以提高液化精度- 实现液化模式的交互控制,包括双击进入和快捷键操作- 添加液化画笔效果,支持推动、膨胀等多种变形模式- 完善网格数据结构,支持顶点数量动态变化时的UV和索引自动调整-优化选中框绘制逻辑,避免与顶点渲染冲突
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.chuangzhou.vivid2D.render.awt;
|
||||
|
||||
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||
|
||||
/**
|
||||
@@ -27,4 +28,7 @@ public interface ModelClickListener {
|
||||
* @param screenY 屏幕坐标系中的 Y 坐标
|
||||
*/
|
||||
default void onModelHover(Mesh2D mesh, float modelX, float modelY, int screenX, int screenY) {}
|
||||
default void onLiquifyModeExited(){};
|
||||
|
||||
default void onLiquifyModeEntered(Mesh2D targetMesh, ModelPart liquifyTargetPart){};
|
||||
}
|
||||
|
||||
@@ -824,12 +824,84 @@ public class ModelLayerPanel extends JPanel {
|
||||
private Mesh2D createQuadForImage(BufferedImage img, String meshName) {
|
||||
float w = img.getWidth();
|
||||
float h = img.getHeight();
|
||||
|
||||
try {
|
||||
Method m = Mesh2D.class.getMethod("createQuad", String.class, float.class, float.class);
|
||||
Object o = m.invoke(null, meshName, w, h);
|
||||
if (o instanceof Mesh2D) return (Mesh2D) o;
|
||||
if (o instanceof Mesh2D) {
|
||||
Mesh2D mesh = (Mesh2D) o;
|
||||
// 对基础四边形进行细分以增加顶点密度
|
||||
return subdivideMeshForLiquify(mesh, 3); // 3级细分
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
try {
|
||||
// 创建高密度网格(细分网格)
|
||||
return createSubdividedQuad(meshName, w, h, 3); // 3级细分
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
throw new RuntimeException("无法创建 Mesh2D(没有合适的工厂或构造函数)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建细分四边形网格以支持更好的液化效果
|
||||
*/
|
||||
private Mesh2D createSubdividedQuad(String name, float width, float height, int subdivisionLevel) {
|
||||
// 计算细分后的网格参数
|
||||
int segments = (int) Math.pow(2, subdivisionLevel); // 每边分段数
|
||||
int vertexCount = (segments + 1) * (segments + 1); // 顶点总数
|
||||
int triangleCount = segments * segments * 2; // 三角形总数
|
||||
|
||||
float[] vertices = new float[vertexCount * 2];
|
||||
float[] uvs = new float[vertexCount * 2];
|
||||
int[] indices = new int[triangleCount * 3];
|
||||
|
||||
float halfW = width / 2f;
|
||||
float halfH = height / 2f;
|
||||
|
||||
// 生成顶点和UV坐标
|
||||
int vertexIndex = 0;
|
||||
for (int y = 0; y <= segments; y++) {
|
||||
for (int x = 0; x <= segments; x++) {
|
||||
// 顶点坐标(从中心点开始)
|
||||
float xPos = -halfW + (x * width) / segments;
|
||||
float yPos = -halfH + (y * height) / segments;
|
||||
|
||||
vertices[vertexIndex * 2] = xPos;
|
||||
vertices[vertexIndex * 2 + 1] = yPos;
|
||||
|
||||
// UV坐标
|
||||
uvs[vertexIndex * 2] = (float) x / segments;
|
||||
uvs[vertexIndex * 2 + 1] = 1f - (float) y / segments; // 翻转V坐标
|
||||
|
||||
vertexIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成三角形索引
|
||||
int index = 0;
|
||||
for (int y = 0; y < segments; y++) {
|
||||
for (int x = 0; x < segments; x++) {
|
||||
int topLeft = y * (segments + 1) + x;
|
||||
int topRight = topLeft + 1;
|
||||
int bottomLeft = (y + 1) * (segments + 1) + x;
|
||||
int bottomRight = bottomLeft + 1;
|
||||
|
||||
// 第一个三角形 (topLeft -> topRight -> bottomLeft)
|
||||
indices[index++] = topLeft;
|
||||
indices[index++] = topRight;
|
||||
indices[index++] = bottomLeft;
|
||||
|
||||
// 第二个三角形 (topRight -> bottomRight -> bottomLeft)
|
||||
indices[index++] = topRight;
|
||||
indices[index++] = bottomRight;
|
||||
indices[index++] = bottomLeft;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用反射创建Mesh2D实例
|
||||
try {
|
||||
Constructor<?> cons = null;
|
||||
for (Constructor<?> c : Mesh2D.class.getDeclaredConstructors()) {
|
||||
@@ -841,27 +913,157 @@ public class ModelLayerPanel extends JPanel {
|
||||
}
|
||||
if (cons != null) {
|
||||
cons.setAccessible(true);
|
||||
float[] vertices = new float[]{
|
||||
-w / 2f, -h / 2f,
|
||||
w / 2f, -h / 2f,
|
||||
w / 2f, h / 2f,
|
||||
-w / 2f, h / 2f
|
||||
};
|
||||
float[] uvs = new float[]{
|
||||
0f, 1f,
|
||||
1f, 1f,
|
||||
1f, 0f,
|
||||
0f, 0f
|
||||
};
|
||||
int[] indices = new int[]{0, 1, 2, 2, 3, 0};
|
||||
Object meshObj = cons.newInstance(meshName, vertices, uvs, indices);
|
||||
if (meshObj instanceof Mesh2D) return (Mesh2D) meshObj;
|
||||
Object meshObj = cons.newInstance(name, vertices, uvs, indices);
|
||||
if (meshObj instanceof Mesh2D) {
|
||||
Mesh2D mesh = (Mesh2D) meshObj;
|
||||
|
||||
// 设置合适的pivot(中心点)
|
||||
mesh.setPivot(0, 0);
|
||||
if (mesh.getOriginalPivot() != null) {
|
||||
mesh.setOriginalPivot(new Vector2f(0, 0));
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
throw new RuntimeException("无法创建 Mesh2D(没有合适的工厂或构造函数)");
|
||||
throw new RuntimeException("无法创建细分网格");
|
||||
}
|
||||
|
||||
/**
|
||||
* 对现有网格进行细分以增加顶点密度
|
||||
*/
|
||||
private Mesh2D subdivideMeshForLiquify(Mesh2D originalMesh, int subdivisionLevel) {
|
||||
if (subdivisionLevel <= 0) return originalMesh;
|
||||
|
||||
try {
|
||||
// 获取原始网格数据
|
||||
float[] origVertices = originalMesh.getVertices();
|
||||
float[] origUVs = originalMesh.getUVs();
|
||||
int[] origIndices = originalMesh.getIndices();
|
||||
|
||||
// 简单的循环细分算法
|
||||
List<Vector2f> newVertices = new ArrayList<>();
|
||||
List<Vector2f> newUVs = new ArrayList<>();
|
||||
List<Integer> newIndices = new ArrayList<>();
|
||||
|
||||
// 添加原始顶点
|
||||
for (int i = 0; i < origVertices.length / 2; i++) {
|
||||
newVertices.add(new Vector2f(origVertices[i * 2], origVertices[i * 2 + 1]));
|
||||
newUVs.add(new Vector2f(origUVs[i * 2], origUVs[i * 2 + 1]));
|
||||
}
|
||||
|
||||
// 对每个三角形进行细分
|
||||
for (int i = 0; i < origIndices.length; i += 3) {
|
||||
int i1 = origIndices[i];
|
||||
int i2 = origIndices[i + 1];
|
||||
int i3 = origIndices[i + 2];
|
||||
|
||||
Vector2f v1 = newVertices.get(i1);
|
||||
Vector2f v2 = newVertices.get(i2);
|
||||
Vector2f v3 = newVertices.get(i3);
|
||||
|
||||
Vector2f uv1 = newUVs.get(i1);
|
||||
Vector2f uv2 = newUVs.get(i2);
|
||||
Vector2f uv3 = newUVs.get(i3);
|
||||
|
||||
// 计算边的中点
|
||||
Vector2f mid12 = new Vector2f(v1).add(v2).mul(0.5f);
|
||||
Vector2f mid23 = new Vector2f(v2).add(v3).mul(0.5f);
|
||||
Vector2f mid31 = new Vector2f(v3).add(v1).mul(0.5f);
|
||||
|
||||
Vector2f uvMid12 = new Vector2f(uv1).add(uv2).mul(0.5f);
|
||||
Vector2f uvMid23 = new Vector2f(uv2).add(uv3).mul(0.5f);
|
||||
Vector2f uvMid31 = new Vector2f(uv3).add(uv1).mul(0.5f);
|
||||
|
||||
// 添加新顶点
|
||||
int mid12Idx = newVertices.size();
|
||||
newVertices.add(mid12);
|
||||
newUVs.add(uvMid12);
|
||||
|
||||
int mid23Idx = newVertices.size();
|
||||
newVertices.add(mid23);
|
||||
newUVs.add(uvMid23);
|
||||
|
||||
int mid31Idx = newVertices.size();
|
||||
newVertices.add(mid31);
|
||||
newUVs.add(uvMid31);
|
||||
|
||||
// 创建4个小三角形
|
||||
// 三角形1: v1, mid12, mid31
|
||||
newIndices.add(i1);
|
||||
newIndices.add(mid12Idx);
|
||||
newIndices.add(mid31Idx);
|
||||
|
||||
// 三角形2: v2, mid23, mid12
|
||||
newIndices.add(i2);
|
||||
newIndices.add(mid23Idx);
|
||||
newIndices.add(mid12Idx);
|
||||
|
||||
// 三角形3: v3, mid31, mid23
|
||||
newIndices.add(i3);
|
||||
newIndices.add(mid31Idx);
|
||||
newIndices.add(mid23Idx);
|
||||
|
||||
// 三角形4: mid12, mid23, mid31
|
||||
newIndices.add(mid12Idx);
|
||||
newIndices.add(mid23Idx);
|
||||
newIndices.add(mid31Idx);
|
||||
}
|
||||
|
||||
// 转换回数组
|
||||
float[] finalVertices = new float[newVertices.size() * 2];
|
||||
float[] finalUVs = new float[newUVs.size() * 2];
|
||||
int[] finalIndices = new int[newIndices.size()];
|
||||
|
||||
for (int i = 0; i < newVertices.size(); i++) {
|
||||
finalVertices[i * 2] = newVertices.get(i).x;
|
||||
finalVertices[i * 2 + 1] = newVertices.get(i).y;
|
||||
|
||||
finalUVs[i * 2] = newUVs.get(i).x;
|
||||
finalUVs[i * 2 + 1] = newUVs.get(i).y;
|
||||
}
|
||||
|
||||
for (int i = 0; i < newIndices.size(); i++) {
|
||||
finalIndices[i] = newIndices.get(i);
|
||||
}
|
||||
|
||||
// 创建新的细分网格
|
||||
Mesh2D subdividedMesh = originalMesh.copy();
|
||||
subdividedMesh.setMeshData(finalVertices, finalUVs, finalIndices);
|
||||
|
||||
// 递归细分直到达到指定级别
|
||||
if (subdivisionLevel > 1) {
|
||||
return subdivideMeshForLiquify(subdividedMesh, subdivisionLevel - 1);
|
||||
}
|
||||
|
||||
return subdividedMesh;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return originalMesh; // 如果细分失败,返回原始网格
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据图像尺寸智能计算细分级别
|
||||
*/
|
||||
private int calculateOptimalSubdivisionLevel(float width, float height) {
|
||||
float area = width * height;
|
||||
|
||||
// 根据面积决定细分级别
|
||||
if (area < 10000) { // 小图像
|
||||
return 2;
|
||||
} else if (area < 50000) { // 中等图像
|
||||
return 3;
|
||||
} else if (area < 200000) { // 大图像
|
||||
return 4;
|
||||
} else { // 超大图像
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.chuangzhou.vivid2D.render.model.util.BoundingBox;
|
||||
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
||||
import com.chuangzhou.vivid2D.render.systems.Camera;
|
||||
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
|
||||
import com.chuangzhou.vivid2D.test.TestModelGLPanel;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Vector2f;
|
||||
import org.lwjgl.glfw.*;
|
||||
@@ -19,11 +20,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.event.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Timer;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -42,7 +45,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
* @author tzdwindows 7
|
||||
* @version 1.1
|
||||
* @since 2025-10-13
|
||||
* @see com.chuangzhou.vivid2D.test.TestModelGLPanel
|
||||
* @see TestModelGLPanel
|
||||
*/
|
||||
public class ModelRenderPanel extends JPanel {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModelRenderPanel.class);
|
||||
@@ -145,6 +148,17 @@ public class ModelRenderPanel extends JPanel {
|
||||
private static final float CAMERA_Z_MIN = -5.0f;
|
||||
private static final float CAMERA_Z_MAX = 5.0f;
|
||||
|
||||
private volatile boolean liquifyMode = false;
|
||||
private volatile ModelPart liquifyTargetPart = null;
|
||||
private volatile Mesh2D liquifyTargetMesh = null;
|
||||
private final Timer doubleClickTimer;
|
||||
private volatile long lastClickTime = 0;
|
||||
private static final int DOUBLE_CLICK_INTERVAL = 300; // 双击间隔(毫秒)
|
||||
|
||||
private float liquifyBrushSize = 50.0f;
|
||||
private float liquifyBrushStrength = 2.0f;
|
||||
private ModelPart.LiquifyMode currentLiquifyMode = ModelPart.LiquifyMode.PUSH;
|
||||
|
||||
// ================== 摄像机控制方法 ==================
|
||||
|
||||
/**
|
||||
@@ -207,6 +221,11 @@ public class ModelRenderPanel extends JPanel {
|
||||
this.operationHistory = OperationHistoryGlobal.getInstance();
|
||||
initialize();
|
||||
initKeyboardShortcuts();
|
||||
|
||||
doubleClickTimer = new Timer(DOUBLE_CLICK_INTERVAL, e -> {
|
||||
handleSingleClick();
|
||||
});
|
||||
doubleClickTimer.setRepeats(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,6 +239,229 @@ public class ModelRenderPanel extends JPanel {
|
||||
this.operationHistory = OperationHistoryGlobal.getInstance();
|
||||
initialize();
|
||||
initKeyboardShortcuts();
|
||||
doubleClickTimer = new Timer(DOUBLE_CLICK_INTERVAL, e -> {
|
||||
// 单单击超时处理
|
||||
handleSingleClick();
|
||||
});
|
||||
doubleClickTimer.setRepeats(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 处理双击事件
|
||||
*/
|
||||
private void handleDoubleClick(MouseEvent e) {
|
||||
if (liquifyMode) {
|
||||
// 如果在液化模式下双击,退出液化模式
|
||||
exitLiquifyMode();
|
||||
return;
|
||||
}
|
||||
|
||||
final int screenX = e.getX();
|
||||
final int screenY = e.getY();
|
||||
|
||||
executeInGLContext(() -> {
|
||||
try {
|
||||
// 转换屏幕坐标到模型坐标
|
||||
float[] modelCoords = screenToModelCoordinates(screenX, screenY);
|
||||
if (modelCoords == null) return;
|
||||
|
||||
float modelX = modelCoords[0];
|
||||
float modelY = modelCoords[1];
|
||||
|
||||
// 检测双击的网格
|
||||
Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY);
|
||||
if (clickedMesh != null) {
|
||||
enterLiquifyMode(clickedMesh);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("处理双击时出错", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 进入液化模式
|
||||
*/
|
||||
private void enterLiquifyMode(Mesh2D targetMesh) {
|
||||
liquifyMode = true;
|
||||
liquifyTargetMesh = targetMesh;
|
||||
liquifyTargetPart = findPartByMesh(targetMesh);
|
||||
|
||||
if (liquifyTargetPart != null) {
|
||||
liquifyTargetPart.setStartLiquefy(true);
|
||||
}
|
||||
|
||||
// 设置液化模式光标
|
||||
setCursor(createLiquifyCursor());
|
||||
|
||||
logger.info("进入液化模式: {}", targetMesh.getName());
|
||||
|
||||
// 通知监听器
|
||||
for (ModelClickListener listener : clickListeners) {
|
||||
try {
|
||||
listener.onLiquifyModeEntered(targetMesh, liquifyTargetPart);
|
||||
} catch (Exception ex) {
|
||||
logger.error("液化模式进入事件监听器执行出错", ex);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出液化模式
|
||||
*/
|
||||
private void exitLiquifyMode() {
|
||||
executeInGLContext(()->{
|
||||
liquifyMode = false;
|
||||
if (liquifyTargetPart != null) {
|
||||
liquifyTargetPart.setStartLiquefy(false);
|
||||
}
|
||||
liquifyTargetMesh = null;
|
||||
liquifyTargetPart = null;
|
||||
|
||||
// 恢复默认光标
|
||||
updateCursorForHoverState();
|
||||
|
||||
logger.info("退出液化模式");
|
||||
|
||||
// 通知监听器
|
||||
for (ModelClickListener listener : clickListeners) {
|
||||
try {
|
||||
listener.onLiquifyModeExited();
|
||||
} catch (Exception ex) {
|
||||
logger.error("液化模式退出事件监听器执行出错", ex);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 液化模式下的单击处理(应用液化效果)
|
||||
*/
|
||||
private void handleLiquifyClick() {
|
||||
Point mousePos = getMousePosition();
|
||||
if (mousePos == null || liquifyTargetPart == null) return;
|
||||
|
||||
logger.debug("液化模式单击: {}", mousePos);
|
||||
|
||||
executeInGLContext(() -> {
|
||||
try {
|
||||
float[] modelCoords = screenToModelCoordinates(mousePos.x, mousePos.y);
|
||||
if (modelCoords == null) return;
|
||||
|
||||
float modelX = modelCoords[0];
|
||||
float modelY = modelCoords[1];
|
||||
|
||||
// 应用液化效果
|
||||
Vector2f brushCenter = new Vector2f(modelX, modelY);
|
||||
|
||||
// 判断是否按住Ctrl键,决定是否创建顶点
|
||||
boolean createVertices = ctrlPressed;
|
||||
|
||||
liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize,
|
||||
liquifyBrushStrength, currentLiquifyMode, 1, createVertices);
|
||||
|
||||
//logger.info("应用液化效果: 位置({}, {}), 大小{}, 强度{}, 模式{}, 创建顶点{}",
|
||||
// modelX, modelY, liquifyBrushSize, liquifyBrushStrength,
|
||||
// currentLiquifyMode, createVertices);
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("应用液化效果时出错", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建液化模式光标
|
||||
*/
|
||||
private Cursor createLiquifyCursor() {
|
||||
// 创建自定义液化光标(圆圈)
|
||||
int size = 32;
|
||||
BufferedImage cursorImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = cursorImg.createGraphics();
|
||||
|
||||
// 设置抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 绘制透明背景
|
||||
g2d.setColor(new Color(0, 0, 0, 0));
|
||||
g2d.fillRect(0, 0, size, size);
|
||||
|
||||
// 绘制圆圈
|
||||
int center = size / 2;
|
||||
int radius = (int)(liquifyBrushSize * 0.1f); // 根据画笔大小缩放光标
|
||||
|
||||
// 外圈
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.setStroke(new BasicStroke(2f));
|
||||
g2d.drawOval(center - radius, center - radius, radius * 2, radius * 2);
|
||||
|
||||
// 内圈
|
||||
g2d.setColor(new Color(255, 100, 100, 150));
|
||||
g2d.setStroke(new BasicStroke(1f));
|
||||
g2d.drawOval(center - radius / 2, center - radius / 2, radius, radius);
|
||||
|
||||
// 中心点
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.fillOval(center - 2, center - 2, 4, 4);
|
||||
|
||||
g2d.dispose();
|
||||
|
||||
return Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(center, center), "LiquifyCursor");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置液化画笔大小
|
||||
*/
|
||||
public void setLiquifyBrushSize(float size) {
|
||||
this.liquifyBrushSize = Math.max(1.0f, Math.min(500.0f, size));
|
||||
if (liquifyMode) {
|
||||
setCursor(createLiquifyCursor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置液化画笔强度
|
||||
*/
|
||||
public void setLiquifyBrushStrength(float strength) {
|
||||
this.liquifyBrushStrength = Math.max(0.0f, Math.min(2.0f, strength));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置液化模式
|
||||
*/
|
||||
public void setLiquifyMode(ModelPart.LiquifyMode mode) {
|
||||
this.currentLiquifyMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前液化状态
|
||||
*/
|
||||
public boolean isInLiquifyMode() {
|
||||
return liquifyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取液化目标网格
|
||||
*/
|
||||
public Mesh2D getLiquifyTargetMesh() {
|
||||
return liquifyTargetMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单单击事件
|
||||
*/
|
||||
private void handleSingleClick() {
|
||||
if (liquifyMode) {
|
||||
handleLiquifyClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -538,8 +780,14 @@ public class ModelRenderPanel extends JPanel {
|
||||
*/
|
||||
public void clearSelectedMeshes() {
|
||||
executeInGLContext(() -> {
|
||||
// 如果当前在液化模式,先退出液化模式
|
||||
if (liquifyMode) {
|
||||
exitLiquifyMode();
|
||||
}
|
||||
|
||||
for (Mesh2D mesh : selectedMeshes) {
|
||||
mesh.setSelected(false);
|
||||
mesh.setSuspension(false);
|
||||
mesh.clearMultiSelection();
|
||||
}
|
||||
selectedMeshes.clear();
|
||||
@@ -616,7 +864,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
if (model == null) return allMeshes;
|
||||
|
||||
try {
|
||||
java.util.List<ModelPart> parts = model.getParts();
|
||||
List<ModelPart> parts = model.getParts();
|
||||
if (parts == null) return allMeshes;
|
||||
|
||||
for (ModelPart part : parts) {
|
||||
@@ -638,7 +886,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
if (part == null) return;
|
||||
|
||||
// 添加当前部件的网格
|
||||
java.util.List<Mesh2D> meshes = part.getMeshes();
|
||||
List<Mesh2D> meshes = part.getMeshes();
|
||||
if (meshes != null) {
|
||||
for (Mesh2D mesh : meshes) {
|
||||
if (mesh != null && mesh.isVisible()) {
|
||||
@@ -697,9 +945,9 @@ public class ModelRenderPanel extends JPanel {
|
||||
// 创建渲染线程
|
||||
startRendering();
|
||||
|
||||
this.addComponentListener(new java.awt.event.ComponentAdapter() {
|
||||
this.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(java.awt.event.ComponentEvent e) {
|
||||
public void componentResized(ComponentEvent e) {
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
@@ -813,6 +1061,10 @@ public class ModelRenderPanel extends JPanel {
|
||||
shiftPressed = true;
|
||||
} else if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
|
||||
ctrlPressed = true;
|
||||
if (liquifyMode && liquifyTargetMesh != null) {
|
||||
liquifyTargetMesh.setRenderVertices(true);
|
||||
logger.debug("液化模式下按住Ctrl,开启顶点渲染");
|
||||
}
|
||||
} else if (e.getKeyCode() == KeyEvent.VK_A && ctrlPressed) {
|
||||
// Ctrl+A 全选
|
||||
e.consume(); // 阻止默认行为
|
||||
@@ -826,6 +1078,10 @@ public class ModelRenderPanel extends JPanel {
|
||||
shiftPressed = false;
|
||||
} else if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
|
||||
ctrlPressed = false;
|
||||
if (liquifyMode && liquifyTargetMesh != null) {
|
||||
liquifyTargetMesh.setRenderVertices(false);
|
||||
logger.debug("液化模式下松开Ctrl,关闭顶点渲染");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -897,11 +1153,11 @@ public class ModelRenderPanel extends JPanel {
|
||||
*/
|
||||
private void handleMousePressed(MouseEvent e) {
|
||||
if (!contextInitialized) return;
|
||||
|
||||
final int screenX = e.getX();
|
||||
final int screenY = e.getY();
|
||||
requestFocusInWindow();
|
||||
|
||||
// 首先处理中键拖拽(摄像机控制),在任何模式下都可用
|
||||
if (SwingUtilities.isMiddleMouseButton(e)) {
|
||||
cameraDragging = true;
|
||||
lastCameraDragX = screenX;
|
||||
@@ -917,6 +1173,23 @@ public class ModelRenderPanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
// 液化模式下的左键处理
|
||||
if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) {
|
||||
// 在液化模式下,左键按下直接开始液化操作
|
||||
currentDragMode = DragMode.NONE; // 液化模式使用特殊拖拽逻辑
|
||||
logger.debug("液化模式下开始拖拽");
|
||||
|
||||
// 立即应用一次液化效果
|
||||
handleLiquifyClick();
|
||||
return;
|
||||
}
|
||||
|
||||
// 非液化模式的正常处理
|
||||
if (liquifyMode) {
|
||||
// 液化模式下只处理左键和中键,其他按钮忽略
|
||||
return;
|
||||
}
|
||||
|
||||
shiftDuringDrag = e.isShiftDown();
|
||||
|
||||
executeInGLContext(() -> {
|
||||
@@ -1375,6 +1648,14 @@ public class ModelRenderPanel extends JPanel {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 液化模式下的拖拽处理(优先于其他模式)
|
||||
if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) {
|
||||
handleLiquifyDrag(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通模式下的拖拽处理
|
||||
if (currentDragMode == DragMode.NONE) return;
|
||||
|
||||
final int screenX = e.getX();
|
||||
@@ -1413,6 +1694,41 @@ public class ModelRenderPanel extends JPanel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 液化模式下的拖拽处理(连续应用液化效果)
|
||||
*/
|
||||
private void handleLiquifyDrag(MouseEvent e) {
|
||||
if (!liquifyMode || liquifyTargetPart == null) return;
|
||||
|
||||
final int screenX = e.getX();
|
||||
final int screenY = e.getY();
|
||||
|
||||
executeInGLContext(() -> {
|
||||
try {
|
||||
float[] modelCoords = screenToModelCoordinates(screenX, screenY);
|
||||
if (modelCoords == null) return;
|
||||
|
||||
float modelX = modelCoords[0];
|
||||
float modelY = modelCoords[1];
|
||||
|
||||
// 应用液化效果
|
||||
Vector2f brushCenter = new Vector2f(modelX, modelY);
|
||||
|
||||
// 判断是否按住Ctrl键,决定是否创建顶点
|
||||
boolean createVertices = ctrlPressed;
|
||||
|
||||
liquifyTargetPart.applyLiquify(brushCenter, liquifyBrushSize,
|
||||
liquifyBrushStrength, currentLiquifyMode, 1, createVertices);
|
||||
|
||||
//logger.info("拖拽应用液化效果: 位置({}, {}), 大小{}, 强度{}, 模式{}, 创建顶点{}",
|
||||
// modelX, modelY, liquifyBrushSize, liquifyBrushStrength,
|
||||
// currentLiquifyMode, createVertices);
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("应用液化拖拽效果时出错", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理移动中心点拖拽
|
||||
@@ -1588,12 +1904,22 @@ public class ModelRenderPanel extends JPanel {
|
||||
* 处理鼠标释放事件(结束拖拽并记录操作历史)
|
||||
*/
|
||||
private void handleMouseReleased(MouseEvent e) {
|
||||
if (cameraDragging && SwingUtilities.isMiddleMouseButton(e)) {
|
||||
// 首先处理摄像机拖拽释放
|
||||
if (cameraDragging && (SwingUtilities.isMiddleMouseButton(e) || liquifyMode)) {
|
||||
cameraDragging = false;
|
||||
// 恢复悬停状态的光标
|
||||
updateCursorForHoverState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 液化模式下的释放处理
|
||||
if (liquifyMode && SwingUtilities.isLeftMouseButton(e)) {
|
||||
// 液化模式下不需要记录操作历史,直接重置状态
|
||||
currentDragMode = DragMode.NONE;
|
||||
logger.debug("液化拖拽结束");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentDragMode != DragMode.NONE) {
|
||||
// 记录操作历史
|
||||
//executeInGLContext(() -> {
|
||||
@@ -1693,37 +2019,47 @@ public class ModelRenderPanel extends JPanel {
|
||||
final int screenX = e.getX();
|
||||
final int screenY = e.getY();
|
||||
|
||||
// 在 GL 上下文线程中执行点击检测
|
||||
executeInGLContext(() -> {
|
||||
try {
|
||||
// 转换屏幕坐标到模型坐标
|
||||
float[] modelCoords = screenToModelCoordinates(screenX, screenY);
|
||||
if (modelCoords == null) return;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
boolean isDoubleClick = (currentTime - lastClickTime) < DOUBLE_CLICK_INTERVAL;
|
||||
lastClickTime = currentTime;
|
||||
|
||||
float modelX = modelCoords[0];
|
||||
float modelY = modelCoords[1];
|
||||
if (isDoubleClick) {
|
||||
// 取消单单击计时器
|
||||
doubleClickTimer.stop();
|
||||
handleDoubleClick(e);
|
||||
} else {
|
||||
executeInGLContext(() -> {
|
||||
try {
|
||||
// 转换屏幕坐标到模型坐标
|
||||
float[] modelCoords = screenToModelCoordinates(screenX, screenY);
|
||||
if (modelCoords == null) return;
|
||||
|
||||
logger.debug("点击位置:({}, {})", modelX, modelY);
|
||||
// 检测点击的网格
|
||||
Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY);
|
||||
float modelX = modelCoords[0];
|
||||
float modelY = modelCoords[1];
|
||||
|
||||
if (clickedMesh == null && !e.isControlDown() && !e.isShiftDown()) {
|
||||
clearSelectedMeshes();
|
||||
logger.debug("点击空白处,取消所有选择");
|
||||
}
|
||||
logger.debug("点击位置:({}, {})", modelX, modelY);
|
||||
// 检测点击的网格
|
||||
Mesh2D clickedMesh = findMeshAtPosition(modelX, modelY);
|
||||
|
||||
// 触发点击事件
|
||||
for (ModelClickListener listener : clickListeners) {
|
||||
try {
|
||||
listener.onModelClicked(clickedMesh, modelX, modelY, screenX, screenY);
|
||||
} catch (Exception ex) {
|
||||
logger.error("点击事件监听器执行出错", ex);
|
||||
if (clickedMesh == null && !e.isControlDown() && !e.isShiftDown()) {
|
||||
clearSelectedMeshes();
|
||||
logger.debug("点击空白处,取消所有选择");
|
||||
}
|
||||
|
||||
// 触发点击事件
|
||||
for (ModelClickListener listener : clickListeners) {
|
||||
try {
|
||||
listener.onModelClicked(clickedMesh, modelX, modelY, screenX, screenY);
|
||||
} catch (Exception ex) {
|
||||
logger.error("点击事件监听器执行出错", ex);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("处理鼠标点击时出错", ex);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("处理鼠标点击时出错", ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
doubleClickTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1756,6 +2092,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
if (newHoveredMesh != hoveredMesh) {
|
||||
if (hoveredMesh != null) {
|
||||
hoveredMesh.setSuspension(false);
|
||||
hoveredMesh.setRenderVertices(false);
|
||||
}
|
||||
|
||||
hoveredMesh = newHoveredMesh;
|
||||
@@ -1891,7 +2228,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
float checkX = modelX;
|
||||
float checkY = modelY;
|
||||
|
||||
java.util.List<ModelPart> parts = model.getParts();
|
||||
List<ModelPart> parts = model.getParts();
|
||||
if (parts == null || parts.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1901,7 +2238,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
ModelPart part = parts.get(i);
|
||||
if (part == null || !part.isVisible()) continue;
|
||||
|
||||
java.util.List<Mesh2D> meshes = part.getMeshes();
|
||||
List<Mesh2D> meshes = part.getMeshes();
|
||||
if (meshes == null || meshes.isEmpty()) continue;
|
||||
|
||||
for (int m = meshes.size() - 1; m >= 0; m--) {
|
||||
@@ -1940,7 +2277,7 @@ public class ModelRenderPanel extends JPanel {
|
||||
if (model == null) return null;
|
||||
|
||||
try {
|
||||
java.lang.reflect.Method getBoundsMethod = model.getClass().getMethod("getBounds");
|
||||
Method getBoundsMethod = model.getClass().getMethod("getBounds");
|
||||
return (BoundingBox) getBoundsMethod.invoke(model);
|
||||
} catch (Exception e) {
|
||||
logger.debug("无法获取模型边界", e);
|
||||
@@ -1955,8 +2292,8 @@ public class ModelRenderPanel extends JPanel {
|
||||
if (model == null) return null;
|
||||
|
||||
try {
|
||||
java.lang.reflect.Method getMeshesMethod = model.getClass().getMethod("getMeshes");
|
||||
java.util.List<Mesh2D> meshes = (java.util.List<Mesh2D>) getMeshesMethod.invoke(model);
|
||||
Method getMeshesMethod = model.getClass().getMethod("getMeshes");
|
||||
List<Mesh2D> meshes = (List<Mesh2D>) getMeshesMethod.invoke(model);
|
||||
return meshes.isEmpty() ? null : meshes.get(0);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
|
||||
@@ -589,6 +589,9 @@ public class OperationHistoryGlobal {
|
||||
case "BATCH_TRANSFORM":
|
||||
executeBatchTransform(params);
|
||||
break;
|
||||
case "LIQUIFY":
|
||||
executeLiquify(params);
|
||||
break;
|
||||
default:
|
||||
LOGGER.debug("执行操作: {}", operationType);
|
||||
break;
|
||||
@@ -631,6 +634,9 @@ public class OperationHistoryGlobal {
|
||||
case "BATCH_TRANSFORM":
|
||||
undoBatchTransform(params);
|
||||
break;
|
||||
case "LIQUIFY":
|
||||
undoLiquify(params);
|
||||
break;
|
||||
default:
|
||||
//System.out.println("撤回操作: " + operationType);
|
||||
break;
|
||||
@@ -639,6 +645,64 @@ public class OperationHistoryGlobal {
|
||||
|
||||
// ============ 批量变换操作方法 ============
|
||||
|
||||
private void executeLiquify(Object... params) {
|
||||
// 重做液化操作:应用液化后的状态
|
||||
if (params.length >= 2) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Mesh2D, OperationHistoryGlobal.MeshState> afterStates =
|
||||
(Map<Mesh2D, OperationHistoryGlobal.MeshState>) params[1];
|
||||
applyLiquifyStates(afterStates);
|
||||
}
|
||||
}
|
||||
|
||||
private void undoLiquify(Object... params) {
|
||||
// 撤回液化操作:恢复液化前的状态
|
||||
if (params.length >= 1) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Mesh2D, OperationHistoryGlobal.MeshState> beforeStates =
|
||||
(Map<Mesh2D, OperationHistoryGlobal.MeshState>) params[0];
|
||||
applyLiquifyStates(beforeStates);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyLiquifyStates(Map<Mesh2D, OperationHistoryGlobal.MeshState> states) {
|
||||
if (states == null || states.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<Mesh2D, OperationHistoryGlobal.MeshState> entry : states.entrySet()) {
|
||||
Mesh2D mesh = entry.getKey();
|
||||
OperationHistoryGlobal.MeshState state = entry.getValue();
|
||||
|
||||
if (mesh == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 恢复顶点数据
|
||||
if (state.vertices != null && state.vertices.length > 0) {
|
||||
mesh.setVertices(state.vertices, true); // true表示同时更新originalVertices
|
||||
} else {
|
||||
}
|
||||
|
||||
// 恢复原始顶点数据(作为备份)
|
||||
if (state.originalVertices != null && state.originalVertices.length > 0) {
|
||||
mesh.setOriginalVertices(state.originalVertices);
|
||||
}
|
||||
|
||||
// 恢复原始中心点
|
||||
if (state.originalPivot != null) {
|
||||
mesh.setOriginalPivot(state.originalPivot);
|
||||
}
|
||||
|
||||
// 强制更新边界
|
||||
mesh.updateBounds();
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBatchTransformRecord(Object... params) {
|
||||
if (params.length >= 3 && params[0] instanceof ModelPart) {
|
||||
ModelPart part = (ModelPart) params[0];
|
||||
@@ -1126,14 +1190,14 @@ public class OperationHistoryGlobal {
|
||||
}
|
||||
|
||||
public static class MeshState {
|
||||
String name;
|
||||
float[] vertices;
|
||||
float[] originalVertices;
|
||||
Vector2f originalPivot;
|
||||
public String name;
|
||||
public float[] vertices;
|
||||
public float[] originalVertices;
|
||||
public Vector2f originalPivot;
|
||||
Object texture;
|
||||
|
||||
MeshState(String name, float[] vertices, float[] originalVertices,
|
||||
Vector2f originalPivot, Object texture) {
|
||||
public MeshState(String name, float[] vertices, float[] originalVertices,
|
||||
Vector2f originalPivot, Object texture) {
|
||||
this.name = name;
|
||||
this.vertices = vertices != null ? vertices.clone() : null;
|
||||
this.originalVertices = originalVertices != null ? originalVertices.clone() : null;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -242,19 +242,8 @@ public class PartData implements Serializable {
|
||||
if (stroke.points != null) {
|
||||
for (LiquifyPointData p : stroke.points) {
|
||||
try {
|
||||
part.applyLiquify(new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
|
||||
part.applyLiquify(new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations, true);
|
||||
} catch (Exception e) {
|
||||
// 如果 applyLiquify 不存在或签名不匹配,则尝试通过反射调用名为 applyLiquify 的方法
|
||||
try {
|
||||
java.lang.reflect.Method am = part.getClass().getMethod("applyLiquify", Vector2f.class, float.class, float.class, ModelPart.LiquifyMode.class, int.class);
|
||||
am.invoke(part, new Vector2f(p.x, p.y), stroke.radius, stroke.strength, modeEnum, stroke.iterations);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// 无法恢复液化(该 ModelPart 可能不支持液化存储/播放),跳过
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,17 @@ public class Mesh2D {
|
||||
private Vector2f pivot = new Vector2f(0, 0);
|
||||
private Vector2f originalPivot = new Vector2f(0, 0);
|
||||
private boolean isSuspension = false;
|
||||
private boolean isRenderVertices = false;
|
||||
|
||||
// ==================== 多选支持 ====================
|
||||
private final List<Mesh2D> multiSelectedParts = new ArrayList<>();
|
||||
private final BoundingBox multiSelectionBounds = new BoundingBox();
|
||||
private boolean multiSelectionDirty = true;
|
||||
|
||||
// ==================== 液化状态渲染 ====================
|
||||
private boolean showLiquifyOverlay = false;
|
||||
private Vector4f liquifyOverlayColor = new Vector4f(1.0f, 0.5f, 0.0f, 0.3f); // 半透明橙色
|
||||
|
||||
// ==================== 常量 ====================
|
||||
public static final int POINTS = 0;
|
||||
public static final int LINES = 1;
|
||||
@@ -121,10 +126,347 @@ public class Mesh2D {
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为渲染顶点
|
||||
*/
|
||||
public void setRenderVertices(boolean renderVertices) {
|
||||
isRenderVertices = renderVertices;
|
||||
}
|
||||
|
||||
public void setModelPart(ModelPart modelPart) {
|
||||
this.modelPart = modelPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否显示液化覆盖层
|
||||
*/
|
||||
public void setShowLiquifyOverlay(boolean show) {
|
||||
this.showLiquifyOverlay = show;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制液化状态覆盖层 - 渲染所有顶点
|
||||
*/
|
||||
private void drawLiquifyOverlay(int shaderProgram, Matrix3f modelMatrix) {
|
||||
if (!showLiquifyOverlay) return;
|
||||
|
||||
RenderSystem.pushState();
|
||||
try {
|
||||
// 使用固色着色器
|
||||
ShaderProgram solidShader = ShaderManagement.getShaderProgram("Solid Color Shader");
|
||||
if (solidShader != null && solidShader.programId != 0) {
|
||||
solidShader.use();
|
||||
|
||||
// 设置模型矩阵
|
||||
int modelLoc = solidShader.getUniformLocation("uModelMatrix");
|
||||
if (modelLoc != -1) {
|
||||
RenderSystem.uniformMatrix3(modelLoc, modelMatrix);
|
||||
}
|
||||
|
||||
// 设置液化覆盖层颜色
|
||||
int colorLoc = solidShader.getUniformLocation("uColor");
|
||||
if (colorLoc != -1) {
|
||||
RenderSystem.uniform4f(colorLoc, liquifyOverlayColor);
|
||||
}
|
||||
}
|
||||
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
Tesselator t = Tesselator.getInstance();
|
||||
BufferBuilder bb = t.getBuilder();
|
||||
|
||||
if (isRenderVertices && vertices != null && vertices.length >= 6) {
|
||||
// ============ 显示顶点模式 ============
|
||||
// 1. 绘制所有顶点组成的多边形填充
|
||||
bb.begin(GL11.GL_TRIANGLES, vertices.length / 2 * 3);
|
||||
bb.setColor(liquifyOverlayColor);
|
||||
|
||||
// 使用三角形扇绘制填充
|
||||
Vector2f center = new Vector2f(vertices[0], vertices[1]);
|
||||
for (int i = 1; i < vertices.length / 2 - 1; i++) {
|
||||
int baseIndex1 = i * 2;
|
||||
int baseIndex2 = (i + 1) * 2;
|
||||
|
||||
bb.vertex(center.x, center.y, 0f, 0f);
|
||||
bb.vertex(vertices[baseIndex1], vertices[baseIndex1 + 1], 0f, 0f);
|
||||
bb.vertex(vertices[baseIndex2], vertices[baseIndex2 + 1], 0f, 0f);
|
||||
}
|
||||
t.end();
|
||||
|
||||
// 2. 绘制顶点连线(边框)
|
||||
drawLiquifyVertexLines(bb);
|
||||
|
||||
// 3. 绘制顶点标记
|
||||
drawLiquifyVertexPoints(bb);
|
||||
|
||||
// 4. 绘制液化提示文字
|
||||
drawLiquifyTextAtCenter(bb);
|
||||
|
||||
} else {
|
||||
// 4. 绘制液化状态指示器
|
||||
drawLiquifyStatusIndicator(bb);
|
||||
}
|
||||
|
||||
} finally {
|
||||
RenderSystem.popState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制液化状态指示器(在不显示顶点时)
|
||||
*/
|
||||
private void drawLiquifyStatusIndicator(BufferBuilder bb) {
|
||||
BoundingBox bounds = getBounds();
|
||||
if (bounds == null || !bounds.isValid()) return;
|
||||
|
||||
float centerX = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
|
||||
float centerY = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
|
||||
float width = bounds.getWidth();
|
||||
float height = bounds.getHeight();
|
||||
|
||||
// 计算指示器位置(放在网格右上方,不遮挡内容)
|
||||
float indicatorX = bounds.getMaxX() + Math.max(width, height) * 0.2f;
|
||||
float indicatorY = bounds.getMaxY() + Math.max(width, height) * 0.1f;
|
||||
|
||||
// 1. 绘制简洁的液化状态圆点
|
||||
float dotRadius = Math.max(width, height) * 0.08f;
|
||||
|
||||
// 外圈圆点
|
||||
bb.begin(GL11.GL_TRIANGLE_FAN, 16);
|
||||
bb.setColor(new Vector4f(1.0f, 0.6f, 0.0f, 0.8f)); // 橙色圆点
|
||||
bb.vertex(indicatorX, indicatorY, 0f, 0f); // 中心点
|
||||
for (int i = 0; i <= 16; i++) {
|
||||
float angle = (float) (i * 2 * Math.PI / 16);
|
||||
float x = indicatorX + (float) Math.cos(angle) * dotRadius;
|
||||
float y = indicatorY + (float) Math.sin(angle) * dotRadius;
|
||||
bb.vertex(x, y, 0f, 0f);
|
||||
}
|
||||
bb.endImmediate();
|
||||
|
||||
// 内圈白色圆点
|
||||
float innerRadius = dotRadius * 0.5f;
|
||||
bb.begin(GL11.GL_TRIANGLE_FAN, 12);
|
||||
bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 0.9f)); // 白色内圆
|
||||
bb.vertex(indicatorX, indicatorY, 0f, 0f); // 中心点
|
||||
for (int i = 0; i <= 12; i++) {
|
||||
float angle = (float) (i * 2 * Math.PI / 12);
|
||||
float x = indicatorX + (float) Math.cos(angle) * innerRadius;
|
||||
float y = indicatorY + (float) Math.sin(angle) * innerRadius;
|
||||
bb.vertex(x, y, 0f, 0f);
|
||||
}
|
||||
bb.endImmediate();
|
||||
|
||||
// 2. 绘制简洁的画笔图标
|
||||
drawSimpleBrushIcon(bb, indicatorX, indicatorY, dotRadius);
|
||||
|
||||
// 3. 绘制简洁的提示文字
|
||||
String liquifyText = "Liquify";
|
||||
String hintText = "Ctrl: Show Vertices";
|
||||
|
||||
TextRenderer textRenderer = ModelRender.getTextRenderer();
|
||||
if (textRenderer != null) {
|
||||
float textY = indicatorY + dotRadius + 15f;
|
||||
|
||||
// 主标题
|
||||
float titleWidth = textRenderer.getTextWidth(liquifyText);
|
||||
float titleX = indicatorX - titleWidth / 2.0f;
|
||||
|
||||
// 绘制主标题背景(简洁的圆角效果)
|
||||
bb.begin(GL11.GL_TRIANGLES, 6);
|
||||
bb.setColor(new Vector4f(0.0f, 0.0f, 0.0f, 0.6f)); // 半透明黑色背景
|
||||
bb.vertex(titleX - 6, textY - 12, 0f, 0f);
|
||||
bb.vertex(titleX + titleWidth + 6, textY - 12, 0f, 0f);
|
||||
bb.vertex(titleX + titleWidth + 6, textY + 2, 0f, 0f);
|
||||
bb.vertex(titleX + titleWidth + 6, textY + 2, 0f, 0f);
|
||||
bb.vertex(titleX - 6, textY + 2, 0f, 0f);
|
||||
bb.vertex(titleX - 6, textY - 12, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
|
||||
// 绘制主标题
|
||||
ModelRender.renderText(liquifyText, titleX, textY, new Vector4f(1.0f, 0.8f, 0.0f, 1.0f));
|
||||
|
||||
// 提示文字(小字号)
|
||||
float hintY = textY + 15f;
|
||||
float hintWidth = textRenderer.getTextWidth(hintText);
|
||||
float hintX = indicatorX - hintWidth / 2.0f;
|
||||
|
||||
ModelRender.renderText(hintText, hintX, hintY, new Vector4f(0.8f, 0.8f, 0.8f, 0.7f));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制简洁的画笔图标
|
||||
*/
|
||||
private void drawSimpleBrushIcon(BufferBuilder bb, float centerX, float centerY, float size) {
|
||||
float iconSize = size * 0.6f;
|
||||
|
||||
// 画笔柄(简单的线条)
|
||||
bb.begin(GL11.GL_LINES, 2);
|
||||
bb.setColor(new Vector4f(1.0f, 1.0f, 1.0f, 0.9f));
|
||||
bb.vertex(centerX - iconSize * 0.3f, centerY - iconSize * 0.5f, 0f, 0f);
|
||||
bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
|
||||
// 画笔头(小三角形)
|
||||
bb.begin(GL11.GL_TRIANGLES, 3);
|
||||
bb.setColor(new Vector4f(1.0f, 0.3f, 0.0f, 0.9f));
|
||||
bb.vertex(centerX, centerY + iconSize * 0.3f, 0f, 0f);
|
||||
bb.vertex(centerX - iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f);
|
||||
bb.vertex(centerX + iconSize * 0.2f, centerY + iconSize * 0.6f, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制顶点连线(边框)
|
||||
*/
|
||||
private void drawLiquifyVertexLines(BufferBuilder bb) {
|
||||
final Vector4f LINE_COLOR = new Vector4f(1.0f, 0.8f, 0.0f, 1.0f); // 黄色边框
|
||||
|
||||
if (vertices == null || vertices.length < 4) return;
|
||||
|
||||
// 绘制闭合的多边形边框
|
||||
bb.begin(GL11.GL_LINE_LOOP, vertices.length / 2);
|
||||
bb.setColor(LINE_COLOR);
|
||||
|
||||
for (int i = 0; i < vertices.length / 2; i++) {
|
||||
int baseIndex = i * 2;
|
||||
bb.vertex(vertices[baseIndex], vertices[baseIndex + 1], 0f, 0f);
|
||||
}
|
||||
bb.endImmediate();
|
||||
|
||||
// 如果网格有三角形索引,也绘制内部连线
|
||||
if (indices != null && indices.length >= 3) {
|
||||
bb.begin(GL11.GL_LINES, indices.length * 2);
|
||||
bb.setColor(new Vector4f(1.0f, 0.6f, 0.0f, 0.6f)); // 半透明橙色内部线
|
||||
|
||||
for (int i = 0; i < indices.length; i += 3) {
|
||||
int i1 = indices[i];
|
||||
int i2 = indices[i + 1];
|
||||
int i3 = indices[i + 2];
|
||||
|
||||
// 三条边
|
||||
drawLineBetweenVertices(bb, i1, i2);
|
||||
drawLineBetweenVertices(bb, i2, i3);
|
||||
drawLineBetweenVertices(bb, i3, i1);
|
||||
}
|
||||
bb.endImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在两个顶点之间绘制线段
|
||||
*/
|
||||
private void drawLineBetweenVertices(BufferBuilder bb, int index1, int index2) {
|
||||
if (index1 < 0 || index1 >= vertices.length / 2 ||
|
||||
index2 < 0 || index2 >= vertices.length / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int base1 = index1 * 2;
|
||||
int base2 = index2 * 2;
|
||||
|
||||
bb.vertex(vertices[base1], vertices[base1 + 1], 0f, 0f);
|
||||
bb.vertex(vertices[base2], vertices[base2 + 1], 0f, 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制顶点标记
|
||||
*/
|
||||
private void drawLiquifyVertexPoints(BufferBuilder bb) {
|
||||
final float POINT_SIZE = 4.0f;
|
||||
final Vector4f POINT_COLOR = new Vector4f(1.0f, 0.4f, 0.0f, 1.0f); // 橙色顶点
|
||||
|
||||
if (vertices == null) return;
|
||||
|
||||
// 绘制所有顶点
|
||||
for (int i = 0; i < vertices.length / 2; i++) {
|
||||
int baseIndex = i * 2;
|
||||
float x = vertices[baseIndex];
|
||||
float y = vertices[baseIndex + 1];
|
||||
|
||||
// 绘制小方块表示顶点
|
||||
bb.begin(GL11.GL_TRIANGLES, 6);
|
||||
bb.setColor(POINT_COLOR);
|
||||
|
||||
float halfSize = POINT_SIZE / 2;
|
||||
bb.vertex(x - halfSize, y - halfSize, 0f, 0f);
|
||||
bb.vertex(x + halfSize, y - halfSize, 0f, 0f);
|
||||
bb.vertex(x + halfSize, y + halfSize, 0f, 0f);
|
||||
|
||||
bb.vertex(x + halfSize, y + halfSize, 0f, 0f);
|
||||
bb.vertex(x - halfSize, y + halfSize, 0f, 0f);
|
||||
bb.vertex(x - halfSize, y - halfSize, 0f, 0f);
|
||||
|
||||
bb.endImmediate();
|
||||
|
||||
// 在顶点旁边显示编号(可选)
|
||||
drawVertexNumber(bb, i, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在顶点旁边显示编号
|
||||
*/
|
||||
private void drawVertexNumber(BufferBuilder bb, int vertexIndex, float x, float y) {
|
||||
String numberText = String.valueOf(vertexIndex);
|
||||
TextRenderer textRenderer = ModelRender.getTextRenderer();
|
||||
if (textRenderer != null) {
|
||||
float textWidth = textRenderer.getTextWidth(numberText);
|
||||
float textX = x + 6.0f; // 在顶点右侧显示
|
||||
float textY = y - 4.0f;
|
||||
|
||||
// 绘制文字背景
|
||||
bb.begin(GL11.GL_TRIANGLES, 6);
|
||||
bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f));
|
||||
bb.vertex(textX - 2, textY - 8, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 2, textY - 8, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 2, textY + 2, 0f, 0f);
|
||||
bb.vertex(textX - 2, textY + 2, 0f, 0f);
|
||||
bb.vertex(textX - 2, textY - 8, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
|
||||
// 绘制文字
|
||||
ModelRender.renderText(numberText, textX, textY, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在网格中心绘制液化提示文字
|
||||
*/
|
||||
private void drawLiquifyTextAtCenter(BufferBuilder bb) {
|
||||
String liquifyText = "LIQUIFY MODE";
|
||||
BoundingBox bounds = getBounds();
|
||||
if (bounds == null || !bounds.isValid()) return;
|
||||
|
||||
float centerX = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
|
||||
float textY = bounds.getMaxY() + 20.0f;
|
||||
|
||||
// 使用文本渲染器绘制提示文字
|
||||
Vector4f textColor = new Vector4f(1.0f, 0.8f, 0.0f, 1.0f);
|
||||
TextRenderer textRenderer = ModelRender.getTextRenderer();
|
||||
if (textRenderer != null) {
|
||||
float textWidth = textRenderer.getTextWidth(liquifyText);
|
||||
float textX = centerX - textWidth / 2.0f;
|
||||
|
||||
// 绘制文字背景
|
||||
bb.begin(GL11.GL_TRIANGLES, 6);
|
||||
bb.setColor(new Vector4f(0.1f, 0.1f, 0.1f, 0.8f));
|
||||
bb.vertex(textX - 5, textY - 15, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 5, textY - 15, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 5, textY + 5, 0f, 0f);
|
||||
bb.vertex(textX + textWidth + 5, textY + 5, 0f, 0f);
|
||||
bb.vertex(textX - 5, textY + 5, 0f, 0f);
|
||||
bb.vertex(textX - 5, textY - 15, 0f, 0f);
|
||||
bb.endImmediate();
|
||||
|
||||
// 绘制文字
|
||||
ModelRender.renderText(liquifyText, textX, textY, textColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中心点
|
||||
*/
|
||||
@@ -233,6 +575,106 @@ public class Mesh2D {
|
||||
this.originalVertices = originalVertices != null ? originalVertices.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置顶点数据(支持顶点数量变化)
|
||||
*/
|
||||
public void setVertices(float[] vertices) {
|
||||
if (vertices == null) {
|
||||
throw new IllegalArgumentException("Vertices array cannot be null");
|
||||
}
|
||||
if (vertices.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Vertices array must have even length (x,y pairs)");
|
||||
}
|
||||
|
||||
// 允许顶点数量变化,但需要相应地调整UV数组
|
||||
if (this.vertices.length != vertices.length) {
|
||||
// 顶点数量发生了变化,需要调整UV数组
|
||||
adjustUVsForNewVertexCount(vertices.length / 2);
|
||||
}
|
||||
|
||||
this.vertices = vertices.clone();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据新的顶点数量调整UV数组
|
||||
*/
|
||||
private void adjustUVsForNewVertexCount(int newVertexCount) {
|
||||
int currentVertexCount = getVertexCount();
|
||||
|
||||
if (newVertexCount == currentVertexCount) {
|
||||
return; // 顶点数量没有变化
|
||||
}
|
||||
|
||||
float[] newUVs = new float[newVertexCount * 2];
|
||||
|
||||
if (newVertexCount > currentVertexCount) {
|
||||
// 顶点数量增加,复制现有UV并估算新增顶点的UV
|
||||
System.arraycopy(uvs, 0, newUVs, 0, uvs.length);
|
||||
|
||||
// 为新增的顶点估算UV坐标
|
||||
for (int i = currentVertexCount; i < newVertexCount; i++) {
|
||||
int uvIndex = i * 2;
|
||||
// 使用边界框来估算UV
|
||||
BoundingBox bounds = getBounds();
|
||||
if (bounds != null && bounds.isValid()) {
|
||||
float u = 0.5f; // 默认值
|
||||
float v = 0.5f; // 默认值
|
||||
newUVs[uvIndex] = u;
|
||||
newUVs[uvIndex + 1] = v;
|
||||
} else {
|
||||
newUVs[uvIndex] = 0.5f;
|
||||
newUVs[uvIndex + 1] = 0.5f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 顶点数量减少,截断UV数组
|
||||
System.arraycopy(uvs, 0, newUVs, 0, newUVs.length);
|
||||
}
|
||||
|
||||
this.uvs = newUVs;
|
||||
|
||||
// 如果顶点数量变化很大,可能需要重新生成索引
|
||||
if (Math.abs(newVertexCount - currentVertexCount) > 2) {
|
||||
regenerateIndicesForNewVertexCount(newVertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为新的顶点数量重新生成索引
|
||||
*/
|
||||
private void regenerateIndicesForNewVertexCount(int newVertexCount) {
|
||||
// 简单的三角形扇形索引生成
|
||||
if (newVertexCount >= 3) {
|
||||
List<Integer> newIndices = new ArrayList<>();
|
||||
|
||||
// 使用三角形扇形(适用于凸多边形)
|
||||
for (int i = 1; i < newVertexCount - 1; i++) {
|
||||
newIndices.add(0);
|
||||
newIndices.add(i);
|
||||
newIndices.add(i + 1);
|
||||
}
|
||||
|
||||
this.indices = new int[newIndices.size()];
|
||||
for (int i = 0; i < newIndices.size(); i++) {
|
||||
this.indices[i] = newIndices.get(i);
|
||||
}
|
||||
} else {
|
||||
// 顶点太少,清空索引
|
||||
this.indices = new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置顶点数据(带原始顶点同步)
|
||||
*/
|
||||
public void setVertices(float[] vertices, boolean updateOriginal) {
|
||||
setVertices(vertices);
|
||||
if (updateOriginal && originalVertices != null && originalVertices.length == vertices.length) {
|
||||
this.originalVertices = vertices.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆形网格
|
||||
*/
|
||||
@@ -276,6 +718,138 @@ public class Mesh2D {
|
||||
|
||||
// ==================== 顶点操作 ====================
|
||||
|
||||
/**
|
||||
* 添加新顶点到网格末尾
|
||||
*/
|
||||
public void addVertex(float x, float y, float u, float v) {
|
||||
// 扩展顶点数组
|
||||
float[] newVertices = new float[vertices.length + 2];
|
||||
System.arraycopy(vertices, 0, newVertices, 0, vertices.length);
|
||||
newVertices[vertices.length] = x;
|
||||
newVertices[vertices.length + 1] = y;
|
||||
|
||||
// 扩展UV数组
|
||||
float[] newUVs = new float[uvs.length + 2];
|
||||
System.arraycopy(uvs, 0, newUVs, 0, uvs.length);
|
||||
newUVs[uvs.length] = u;
|
||||
newUVs[uvs.length + 1] = v;
|
||||
|
||||
// 更新数据
|
||||
this.vertices = newVertices;
|
||||
this.uvs = newUVs;
|
||||
|
||||
// 注意:需要手动更新索引数组来包含新顶点
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置插入顶点
|
||||
*/
|
||||
public void insertVertex(int index, float x, float y, float u, float v) {
|
||||
if (index < 0 || index > getVertexCount()) {
|
||||
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
|
||||
}
|
||||
|
||||
// 扩展顶点数组
|
||||
float[] newVertices = new float[vertices.length + 2];
|
||||
System.arraycopy(vertices, 0, newVertices, 0, index * 2);
|
||||
newVertices[index * 2] = x;
|
||||
newVertices[index * 2 + 1] = y;
|
||||
System.arraycopy(vertices, index * 2, newVertices, index * 2 + 2, vertices.length - index * 2);
|
||||
|
||||
// 扩展UV数组
|
||||
float[] newUVs = new float[uvs.length + 2];
|
||||
System.arraycopy(uvs, 0, newUVs, 0, index * 2);
|
||||
newUVs[index * 2] = u;
|
||||
newUVs[index * 2 + 1] = v;
|
||||
System.arraycopy(uvs, index * 2, newUVs, index * 2 + 2, uvs.length - index * 2);
|
||||
|
||||
// 更新数据
|
||||
this.vertices = newVertices;
|
||||
this.uvs = newUVs;
|
||||
|
||||
// 注意:需要更新索引数组来反映顶点位置的变化
|
||||
updateIndicesAfterVertexInsertion(index);
|
||||
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定顶点
|
||||
*/
|
||||
public void removeVertex(int index) {
|
||||
if (index < 0 || index >= getVertexCount()) {
|
||||
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
|
||||
}
|
||||
|
||||
// 缩减顶点数组
|
||||
float[] newVertices = new float[vertices.length - 2];
|
||||
System.arraycopy(vertices, 0, newVertices, 0, index * 2);
|
||||
System.arraycopy(vertices, index * 2 + 2, newVertices, index * 2, vertices.length - index * 2 - 2);
|
||||
|
||||
// 缩减UV数组
|
||||
float[] newUVs = new float[uvs.length - 2];
|
||||
System.arraycopy(uvs, 0, newUVs, 0, index * 2);
|
||||
System.arraycopy(uvs, index * 2 + 2, newUVs, index * 2, uvs.length - index * 2 - 2);
|
||||
|
||||
// 更新数据
|
||||
this.vertices = newVertices;
|
||||
this.uvs = newUVs;
|
||||
|
||||
// 更新索引数组
|
||||
updateIndicesAfterVertexRemoval(index);
|
||||
|
||||
markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在插入顶点后更新索引数组
|
||||
*/
|
||||
private void updateIndicesAfterVertexInsertion(int insertedIndex) {
|
||||
if (indices == null) return;
|
||||
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
if (indices[i] >= insertedIndex) {
|
||||
indices[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在移除顶点后更新索引数组
|
||||
*/
|
||||
private void updateIndicesAfterVertexRemoval(int removedIndex) {
|
||||
if (indices == null) return;
|
||||
|
||||
// 创建新索引数组,移除引用被删除顶点的三角形
|
||||
List<Integer> newIndicesList = new ArrayList<>();
|
||||
for (int i = 0; i < indices.length; i += 3) {
|
||||
int i1 = indices[i];
|
||||
int i2 = indices[i + 1];
|
||||
int i3 = indices[i + 2];
|
||||
|
||||
// 如果三角形包含被删除的顶点,跳过这个三角形
|
||||
if (i1 == removedIndex || i2 == removedIndex || i3 == removedIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 调整索引编号
|
||||
if (i1 > removedIndex) i1--;
|
||||
if (i2 > removedIndex) i2--;
|
||||
if (i3 > removedIndex) i3--;
|
||||
|
||||
newIndicesList.add(i1);
|
||||
newIndicesList.add(i2);
|
||||
newIndicesList.add(i3);
|
||||
}
|
||||
|
||||
// 转换回数组
|
||||
this.indices = new int[newIndicesList.size()];
|
||||
for (int i = 0; i < newIndicesList.size(); i++) {
|
||||
this.indices[i] = newIndicesList.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取顶点数量
|
||||
*/
|
||||
@@ -768,7 +1342,7 @@ public class Mesh2D {
|
||||
}
|
||||
|
||||
// 选中框绘制(需要切换到固色 shader)
|
||||
if (selected) {
|
||||
if (selected && !isRenderVertices) {
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
@@ -797,6 +1371,10 @@ public class Mesh2D {
|
||||
}
|
||||
}
|
||||
|
||||
if (showLiquifyOverlay) {
|
||||
drawLiquifyOverlay(shaderProgram, modelMatrix);
|
||||
}
|
||||
|
||||
if (isSuspension && !selected) {
|
||||
RenderSystem.pushState();
|
||||
|
||||
|
||||
@@ -25,4 +25,46 @@ public class Matrix3fUtils {
|
||||
public static Vector2f transformPointInverse(Matrix3f matrix, Vector2f point) {
|
||||
return transformPointInverse(matrix, point, new Vector2f());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换向量(不考虑平移,只考虑旋转和缩放)
|
||||
*/
|
||||
public static Vector2f transformVector(Matrix3f matrix, Vector2f vector, Vector2f dest) {
|
||||
float x = matrix.m00() * vector.x + matrix.m01() * vector.y;
|
||||
float y = matrix.m10() * vector.x + matrix.m11() * vector.y;
|
||||
return dest.set(x, y);
|
||||
}
|
||||
|
||||
public static Vector2f transformVector(Matrix3f matrix, Vector2f vector) {
|
||||
return transformVector(matrix, vector, new Vector2f());
|
||||
}
|
||||
|
||||
/**
|
||||
* 逆变换向量(不考虑平移,只考虑旋转和缩放的逆)
|
||||
*/
|
||||
public static Vector2f transformVectorInverse(Matrix3f matrix, Vector2f vector, Vector2f dest) {
|
||||
// 计算2x2子矩阵的行列式
|
||||
float det = matrix.m00() * matrix.m11() - matrix.m01() * matrix.m10();
|
||||
|
||||
if (Math.abs(det) < 1e-6f) {
|
||||
return dest.set(vector);
|
||||
}
|
||||
|
||||
float invDet = 1.0f / det;
|
||||
|
||||
// 计算2x2子矩阵的逆
|
||||
float m00 = matrix.m11() * invDet;
|
||||
float m01 = -matrix.m01() * invDet;
|
||||
float m10 = -matrix.m10() * invDet;
|
||||
float m11 = matrix.m00() * invDet;
|
||||
|
||||
float x = vector.x * m00 + vector.y * m01;
|
||||
float y = vector.x * m10 + vector.y * m11;
|
||||
|
||||
return dest.set(x, y);
|
||||
}
|
||||
|
||||
public static Vector2f transformVectorInverse(Matrix3f matrix, Vector2f vector) {
|
||||
return transformVectorInverse(matrix, vector, new Vector2f());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user