feat(render): 添加木偶工具和二级顶点支持- 添加木偶控制点相关字段和方法- 实现木偶控制点的添加、移除和选择功能- 实现基于木偶控制点的网格变形算法

- 添加二级顶点支持及相关操作方法
- 实现二级顶点的渲染和交互功能- 添加变形冲突检测和解决机制
- 实现双线性插值和反距离加权插值算法- 添加控制点影响范围可视化
- 添加二级顶点与网格同步移动功能- 添加变形状态保存和重置功能
This commit is contained in:
tzdwindows 7
2025-10-25 17:05:04 +08:00
parent cdc0843174
commit 1f5752257e
7 changed files with 2977 additions and 110 deletions

View File

@@ -31,4 +31,12 @@ public interface ModelClickListener {
default void onLiquifyModeExited(){};
default void onLiquifyModeEntered(Mesh2D targetMesh, ModelPart liquifyTargetPart){};
default void onSecondaryVertexModeEntered(Mesh2D secondaryVertexTargetMesh){};
default void onSecondaryVertexModeExited(){};
default void onPuppetModeEntered(Mesh2D puppetTargetMesh){};
default void onPuppetModeExited(){};
}

View File

@@ -3,6 +3,7 @@ package com.chuangzhou.vivid2D.render.model;
import com.chuangzhou.vivid2D.render.awt.util.OperationHistoryGlobal;
import com.chuangzhou.vivid2D.render.model.util.BoundingBox;
import com.chuangzhou.vivid2D.render.model.util.Deformer;
import com.chuangzhou.vivid2D.render.model.util.PuppetPin;
import com.chuangzhou.vivid2D.render.systems.Matrix3fUtils;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Matrix3f;
@@ -1668,11 +1669,45 @@ public class ModelPart {
logger.warn("更新网格pivot时出错: {}", e.getMessage());
}
updatePuppetPinsPosition(mesh);
// 标记网格需要更新
mesh.markDirty();
mesh.setBakedToWorld(true);
}
/**
* 更新木偶控制点的位置
*/
private void updatePuppetPinsPosition(Mesh2D mesh) {
if (mesh == null) return;
List<PuppetPin> puppetPins = mesh.getPuppetPins();
if (puppetPins.isEmpty()) return;
// 确保世界变换是最新的
if (transformDirty) {
updateLocalTransform();
recomputeWorldTransformRecursive();
}
for (PuppetPin pin : puppetPins) {
// 获取控制点的原始局部位置
Vector2f originalLocalPos = pin.getOriginalPosition();
// 将原始局部位置变换到世界坐标
Vector2f worldPos = Matrix3fUtils.transformPoint(worldTransform, originalLocalPos);
// 更新控制点的当前位置
pin.setPosition(worldPos.x, worldPos.y);
logger.trace("更新木偶控制点位置: 局部({}, {}) -> 世界({}, {})",
originalLocalPos.x, originalLocalPos.y, worldPos.x, worldPos.y);
}
logger.debug("同步更新了 {} 个木偶控制点的位置", puppetPins.size());
}
public void setPosition(Vector2f pos) {
// 记录旧世界变换和旧位置,用于计算位移
Matrix3f oldWorldTransform = new Matrix3f(this.worldTransform);
@@ -1701,12 +1736,46 @@ public class ModelPart {
mesh.setOriginalPivot(newLocalOriginalPivot);
mesh.setPivot(movedWorldPivot.x, movedWorldPivot.y);
// ==================== 新增:同步更新木偶控制点的原始位置 ====================
updatePuppetPinsOriginalPosition(mesh, oldWorldTransform, dx, dy);
}
// 更新网格顶点位置
updateMeshVertices();
}
/**
* 更新木偶控制点的原始位置
*/
private void updatePuppetPinsOriginalPosition(Mesh2D mesh, Matrix3f oldWorldTransform, float dx, float dy) {
if (mesh == null) return;
List<PuppetPin> puppetPins = mesh.getPuppetPins();
if (puppetPins.isEmpty()) return;
for (PuppetPin pin : puppetPins) {
// 获取控制点的当前原始位置(局部坐标)
Vector2f currentOriginalPos = pin.getOriginalPosition();
// 将原始位置变换到旧的世界坐标系
Vector2f oldWorldPos = Matrix3fUtils.transformPoint(oldWorldTransform, currentOriginalPos);
// 在世界坐标系中应用位移
Vector2f newWorldPos = new Vector2f(oldWorldPos.x + dx, oldWorldPos.y + dy);
// 将新的世界位置逆变换回新的局部坐标系
Vector2f newLocalOriginalPos = Matrix3fUtils.transformPointInverse(this.worldTransform, newWorldPos);
// 更新控制点的原始位置
pin.setOriginalPosition(newLocalOriginalPos.x, newLocalOriginalPos.y);
logger.trace("更新木偶控制点原始位置: 旧局部({}, {}) -> 新局部({}, {})",
currentOriginalPos.x, currentOriginalPos.y, newLocalOriginalPos.x, newLocalOriginalPos.y);
}
}
/**
* 移动部件
*/
@@ -1748,6 +1817,9 @@ public class ModelPart {
Vector2f newLocalOriginalPivot = Matrix3fUtils.transformPointInverse(this.worldTransform, oldWorldPivot);
mesh.setOriginalPivot(newLocalOriginalPivot);
mesh.setPivot(Matrix3fUtils.transformPoint(this.worldTransform, newLocalOriginalPivot));
// ==================== 新增:同步更新木偶控制点的原始位置 ====================
updatePuppetPinsOriginalPositionForTransform(mesh, oldWorldTransform);
}
updateMeshVertices();
@@ -1793,12 +1865,40 @@ public class ModelPart {
Vector2f newLocalOriginalPivot = Matrix3fUtils.transformPointInverse(this.worldTransform, oldWorldPivot);
mesh.setOriginalPivot(newLocalOriginalPivot);
mesh.setPivot(Matrix3fUtils.transformPoint(this.worldTransform, newLocalOriginalPivot));
// ==================== 新增:同步更新木偶控制点的原始位置 ====================
updatePuppetPinsOriginalPositionForTransform(mesh, oldWorldTransform);
}
updateMeshVertices();
triggerEvent("scale");
}
/**
* 为变换操作更新木偶控制点的原始位置
*/
private void updatePuppetPinsOriginalPositionForTransform(Mesh2D mesh, Matrix3f oldWorldTransform) {
if (mesh == null) return;
List<PuppetPin> puppetPins = mesh.getPuppetPins();
if (puppetPins.isEmpty()) return;
for (PuppetPin pin : puppetPins) {
// 获取控制点的当前原始位置(局部坐标)
Vector2f currentOriginalPos = pin.getOriginalPosition();
// 将原始位置变换到旧的世界坐标系
Vector2f oldWorldPos = Matrix3fUtils.transformPoint(oldWorldTransform, currentOriginalPos);
// 将旧的世界位置逆变换回新的局部坐标系
Vector2f newLocalOriginalPos = Matrix3fUtils.transformPointInverse(this.worldTransform, oldWorldPos);
// 更新控制点的原始位置
pin.setOriginalPosition(newLocalOriginalPos.x, newLocalOriginalPos.y);
}
}
public void setScale(float uniformScale) {
// 记录旧的世界变换,用于计算 pivot 的相对位置
@@ -1894,6 +1994,9 @@ public class ModelPart {
mesh.setPivot(worldPivot.x, worldPivot.y);
} catch (Exception ignored) { }
// ==================== 新增:初始化木偶控制点的位置 ====================
initializePuppetPinsPosition(mesh);
// 标记为已烘焙到世界坐标(语义上明确),并确保 bounds/dirty 状态被正确刷新
mesh.setBakedToWorld(true);
@@ -1905,6 +2008,63 @@ public class ModelPart {
boundsDirty = true;
}
/**
* 更新木偶控制点的位置(修复移动问题)
*/
private void updatePuppetPinsForMovement(Mesh2D mesh, Matrix3f oldWorldTransform, float dx, float dy) {
if (mesh == null) return;
List<PuppetPin> puppetPins = mesh.getPuppetPins();
if (puppetPins.isEmpty()) return;
for (PuppetPin pin : puppetPins) {
// 获取控制点的当前位置(世界坐标)
Vector2f currentWorldPos = pin.getPosition();
// 应用相同的位移到控制点
Vector2f newWorldPos = new Vector2f(currentWorldPos.x + dx, currentWorldPos.y + dy);
// 更新控制点的位置
pin.setPosition(newWorldPos.x, newWorldPos.y);
// 同时更新控制点的原始位置,保持一致性
Vector2f currentOriginalPos = pin.getOriginalPosition();
Vector2f newOriginalPos = new Vector2f(currentOriginalPos.x + dx, currentOriginalPos.y + dy);
pin.setOriginalPosition(newOriginalPos.x, newOriginalPos.y);
logger.trace("移动木偶控制点: ({}, {}) -> ({}, {})",
currentWorldPos.x, currentWorldPos.y, newWorldPos.x, newWorldPos.y);
}
logger.debug("移动时同步更新了 {} 个木偶控制点", puppetPins.size());
}
/**
* 初始化木偶控制点的位置
*/
private void initializePuppetPinsPosition(Mesh2D mesh) {
if (mesh == null) return;
List<PuppetPin> puppetPins = mesh.getPuppetPins();
if (puppetPins.isEmpty()) return;
for (PuppetPin pin : puppetPins) {
// 获取控制点的原始局部位置
Vector2f originalLocalPos = pin.getOriginalPosition();
// 将原始局部位置变换到世界坐标
Vector2f worldPos = Matrix3fUtils.transformPoint(this.worldTransform, originalLocalPos);
// 设置控制点的当前位置
pin.setPosition(worldPos.x, worldPos.y);
// 同时保存当前位置为原始位置(确保一致性)
pin.saveAsOriginal();
}
logger.debug("初始化了 {} 个木偶控制点的位置", puppetPins.size());
}
/**
* 设置中心点
*/

View File

@@ -1,12 +1,24 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.PuppetPin;
import com.chuangzhou.vivid2D.render.model.util.SecondaryVertex;
import org.joml.Vector2f;
import org.joml.Vector4f;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author tzdwindows 7
*/
public class MeshData implements Serializable {
private static final long serialVersionUID = 1L;
// 基础网格数据
public String name;
public float[] vertices;
public float[] uvs;
@@ -15,9 +27,45 @@ public class MeshData implements Serializable {
public boolean visible;
public int drawMode;
// 原始顶点数据(用于变形恢复)
public float[] originalVertices;
// 变换相关
public Vector2f pivot;
public Vector2f originalPivot;
// 选择状态
public boolean selected;
public boolean bakedToWorld;
// ==================== 二级顶点支持 ====================
public List<SecondaryVertexData> secondaryVertices = new ArrayList<>();
public boolean showSecondaryVertices = false;
public Vector4f secondaryVertexColor = new Vector4f(0.0f, 1.0f, 0.0f, 1.0f);
public Vector4f selectedSecondaryVertexColor = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f);
public float secondaryVertexSize = 6.0f;
public Integer selectedSecondaryVertexId = null;
// ==================== 木偶工具支持 ====================
public List<PuppetPinData> puppetPins = new ArrayList<>();
public Integer selectedPuppetPinId = null;
public boolean showPuppetPins = true;
public Vector4f puppetPinColor = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f);
public Vector4f selectedPuppetPinColor = new Vector4f(1.0f, 1.0f, 0.0f, 1.0f);
public float puppetPinSize = 8.0f;
// ==================== 液化状态支持 ====================
public boolean showLiquifyOverlay = false;
public Vector4f liquifyOverlayColor = new Vector4f(1.0f, 0.5f, 0.0f, 0.3f);
// ==================== 渲染状态 ====================
public boolean isRenderVertices = false;
public MeshData() {
this.visible = true;
this.drawMode = Mesh2D.TRIANGLES;
this.pivot = new Vector2f(0, 0);
this.originalPivot = new Vector2f(0, 0);
}
public MeshData(Mesh2D mesh) {
@@ -28,19 +76,153 @@ public class MeshData implements Serializable {
this.indices = mesh.getIndices();
this.visible = mesh.isVisible();
this.drawMode = mesh.getDrawMode();
this.selected = mesh.isSelected();
this.bakedToWorld = mesh.isBakedToWorld();
this.isRenderVertices = mesh.isRenderVertices();
// 保存原始顶点数据
this.originalVertices = mesh.getOriginalVertices();
// 保存变换数据
this.pivot = new Vector2f(mesh.getPivot());
this.originalPivot = new Vector2f(mesh.getOriginalPivot());
// 保存二级顶点数据
saveSecondaryVertices(mesh);
// 保存木偶控制点数据
savePuppetPins(mesh);
// 保存液化状态
this.showLiquifyOverlay = mesh.showLiquifyOverlay;
this.liquifyOverlayColor = new Vector4f(mesh.getLiquifyOverlayColor());
if (mesh.getTexture() != null) {
this.textureName = mesh.getTexture().getName();
}
}
/**
* 保存二级顶点数据
*/
private void saveSecondaryVertices(Mesh2D mesh) {
List<SecondaryVertex> vertices = mesh.getSecondaryVertices();
SecondaryVertex selectedVertex = mesh.getSelectedSecondaryVertex();
for (SecondaryVertex vertex : vertices) {
SecondaryVertexData data = new SecondaryVertexData(vertex);
secondaryVertices.add(data);
// 记录选中的二级顶点ID
if (selectedVertex != null && selectedVertex.getId() == vertex.getId()) {
selectedSecondaryVertexId = vertex.getId();
}
}
this.showSecondaryVertices = mesh.isShowSecondaryVertices();
this.secondaryVertexColor = new Vector4f(mesh.secondaryVertexColor);
this.selectedSecondaryVertexColor = new Vector4f(mesh.selectedSecondaryVertexColor);
this.secondaryVertexSize = mesh.secondaryVertexSize;
}
/**
* 保存木偶控制点数据
*/
private void savePuppetPins(Mesh2D mesh) {
List<PuppetPin> pins = mesh.getPuppetPins();
PuppetPin selectedPin = mesh.getSelectedPuppetPin();
for (PuppetPin pin : pins) {
PuppetPinData data = new PuppetPinData(pin);
puppetPins.add(data);
// 记录选中的木偶控制点ID
if (selectedPin != null && selectedPin.getId() == pin.getId()) {
selectedPuppetPinId = pin.getId();
}
}
this.showPuppetPins = mesh.getShowPuppetPins();
this.puppetPinColor = new Vector4f(mesh.getPuppetPinColor());
this.selectedPuppetPinColor = new Vector4f(mesh.getSelectedPuppetPinColor());
this.puppetPinSize = mesh.getPuppetPinSize();
}
public Mesh2D toMesh2D() {
Mesh2D mesh = new Mesh2D(name, vertices, uvs, indices);
mesh.setVisible(visible);
mesh.setDrawMode(drawMode);
mesh.setSelected(selected);
mesh.setBakedToWorld(bakedToWorld);
mesh.setRenderVertices(isRenderVertices);
// 恢复原始顶点数据
if (originalVertices != null) {
mesh.setOriginalVertices(originalVertices);
}
// 恢复变换数据
mesh.setPivot(pivot);
mesh.setOriginalPivot(originalPivot);
// 恢复二级顶点
restoreSecondaryVertices(mesh);
// 恢复木偶控制点
restorePuppetPins(mesh);
// 恢复液化状态
mesh.setShowLiquifyOverlay(showLiquifyOverlay);
mesh.getLiquifyOverlayColor().set(liquifyOverlayColor);
return mesh;
}
/**
* 恢复二级顶点数据
*/
private void restoreSecondaryVertices(Mesh2D mesh) {
for (SecondaryVertexData data : secondaryVertices) {
SecondaryVertex vertex = mesh.addSecondaryVertex(data.position.x, data.position.y, data.uv.x, data.uv.y);
vertex.setId(data.id);
vertex.setOriginalPosition(data.originalPosition);
// 恢复选中状态
if (selectedSecondaryVertexId != null && data.id == selectedSecondaryVertexId) {
mesh.setSelectedSecondaryVertex(vertex);
}
}
mesh.setShowSecondaryVertices(showSecondaryVertices);
mesh.setSecondaryVertexColor(secondaryVertexColor);
mesh.setSelectedSecondaryVertexColor(selectedSecondaryVertexColor);
mesh.setSecondaryVertexSize(secondaryVertexSize);
}
/**
* 恢复木偶控制点数据
*/
private void restorePuppetPins(Mesh2D mesh) {
for (PuppetPinData data : puppetPins) {
PuppetPin pin = mesh.addPuppetPin(data.position.x, data.position.y, data.uv.x, data.uv.y);
pin.setId(data.id);
pin.setOriginalPosition(data.originalPosition);
pin.setInfluenceRadius(data.influenceRadius);
pin.setName(data.name);
// 恢复权重映射
pin.getWeightMap().putAll(data.weightMap);
// 恢复选中状态
if (selectedPuppetPinId != null && data.id == selectedPuppetPinId) {
mesh.setSelectedPuppetPin(pin);
}
}
mesh.setShowPuppetPins(showPuppetPins);
// 注意木偶控制点颜色等设置需要在Mesh2D中添加相应的setter方法
}
public MeshData copy() {
MeshData copy = new MeshData();
copy.name = this.name;
@@ -50,6 +232,123 @@ public class MeshData implements Serializable {
copy.textureName = this.textureName;
copy.visible = this.visible;
copy.drawMode = this.drawMode;
copy.selected = this.selected;
copy.bakedToWorld = this.bakedToWorld;
copy.isRenderVertices = this.isRenderVertices;
// 复制原始顶点数据
copy.originalVertices = this.originalVertices != null ? this.originalVertices.clone() : null;
// 复制变换数据
copy.pivot = new Vector2f(this.pivot);
copy.originalPivot = new Vector2f(this.originalPivot);
// 复制二级顶点数据
copy.secondaryVertices = new ArrayList<>();
for (SecondaryVertexData data : this.secondaryVertices) {
copy.secondaryVertices.add(data.copy());
}
copy.showSecondaryVertices = this.showSecondaryVertices;
copy.secondaryVertexColor = new Vector4f(this.secondaryVertexColor);
copy.selectedSecondaryVertexColor = new Vector4f(this.selectedSecondaryVertexColor);
copy.secondaryVertexSize = this.secondaryVertexSize;
copy.selectedSecondaryVertexId = this.selectedSecondaryVertexId;
// 复制木偶控制点数据
copy.puppetPins = new ArrayList<>();
for (PuppetPinData data : this.puppetPins) {
copy.puppetPins.add(data.copy());
}
copy.selectedPuppetPinId = this.selectedPuppetPinId;
copy.showPuppetPins = this.showPuppetPins;
copy.puppetPinColor = new Vector4f(this.puppetPinColor);
copy.selectedPuppetPinColor = new Vector4f(this.selectedPuppetPinColor);
copy.puppetPinSize = this.puppetPinSize;
// 复制液化状态
copy.showLiquifyOverlay = this.showLiquifyOverlay;
copy.liquifyOverlayColor = new Vector4f(this.liquifyOverlayColor);
return copy;
}
}
// ==================== 内部数据类 ====================
/**
* 二级顶点数据类(可序列化)
*/
public static class SecondaryVertexData implements Serializable {
private static final long serialVersionUID = 1L;
public int id;
public Vector2f position;
public Vector2f originalPosition;
public Vector2f uv;
public boolean selected;
public SecondaryVertexData() {}
public SecondaryVertexData(SecondaryVertex vertex) {
this.id = vertex.getId();
this.position = new Vector2f(vertex.getPosition());
this.originalPosition = new Vector2f(vertex.getOriginalPosition());
this.uv = new Vector2f(vertex.getUV());
this.selected = vertex.isSelected();
}
public SecondaryVertexData copy() {
SecondaryVertexData copy = new SecondaryVertexData();
copy.id = this.id;
copy.position = new Vector2f(this.position);
copy.originalPosition = new Vector2f(this.originalPosition);
copy.uv = new Vector2f(this.uv);
copy.selected = this.selected;
return copy;
}
}
/**
* 木偶控制点数据类(可序列化)
*/
public static class PuppetPinData implements Serializable {
private static final long serialVersionUID = 1L;
public int id;
public Vector2f position;
public Vector2f originalPosition;
public Vector2f uv;
public float influenceRadius;
public boolean selected;
public String name;
public Map<Integer, Float> weightMap;
public PuppetPinData() {
this.weightMap = new HashMap<>();
}
public PuppetPinData(PuppetPin pin) {
this();
this.id = pin.getId();
this.position = new Vector2f(pin.getPosition());
this.originalPosition = new Vector2f(pin.getOriginalPosition());
this.uv = new Vector2f(pin.getUV());
this.influenceRadius = pin.getInfluenceRadius();
this.selected = pin.isSelected();
this.name = pin.getName();
this.weightMap = new HashMap<>(pin.getWeightMap());
}
public PuppetPinData copy() {
PuppetPinData copy = new PuppetPinData();
copy.id = this.id;
copy.position = new Vector2f(this.position);
copy.originalPosition = new Vector2f(this.originalPosition);
copy.uv = new Vector2f(this.uv);
copy.influenceRadius = this.influenceRadius;
copy.selected = this.selected;
copy.name = this.name;
copy.weightMap = new HashMap<>(this.weightMap);
return copy;
}
}
}

View File

@@ -0,0 +1,128 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.util.HashMap;
import java.util.Map;
/**
* @author tzdwindows 7
*/
public class PuppetPin {
private static int NEXT_ID = 0;
private int id;
private Vector2f position;
private Vector2f originalPosition;
private Vector2f uv;
private float influenceRadius = 100.0f;
private boolean selected = false;
private String name;
// 权重贴图(顶点索引 -> 权重值)
private Map<Integer, Float> weightMap = new HashMap<>();
public PuppetPin(float x, float y, float u, float v) {
this.id = NEXT_ID++;
this.position = new Vector2f(x, y);
this.originalPosition = new Vector2f(x, y);
this.uv = new Vector2f(u, v);
this.name = "Pin_" + id;
}
// 计算顶点权重(基于距离的衰减)
public float calculateWeight(Vector2f vertexPos, int vertexIndex) {
float distance = position.distance(vertexPos);
if (distance > influenceRadius) {
return 0.0f;
}
// 使用平滑的衰减函数
float normalizedDistance = distance / influenceRadius;
float weight = (float) (Math.cos(normalizedDistance * Math.PI) + 1) / 2.0f;
return weight;
}
// 预计算所有顶点权重
public void precomputeWeights(Mesh2D mesh) {
weightMap.clear();
for (int i = 0; i < mesh.getVertexCount(); i++) {
Vector2f vertexPos = mesh.getVertex(i);
float weight = calculateWeight(vertexPos, i);
if (weight > 0.01f) { // 只存储有影响的权重
weightMap.put(i, weight);
}
}
}
// ==================== 新增方法 ====================
/**
* 设置原始位置
*/
public void setOriginalPosition(float x, float y) {
this.originalPosition.set(x, y);
}
/**
* 设置原始位置
*/
public void setOriginalPosition(Vector2f pos) {
this.originalPosition.set(pos);
}
/**
* 获取UV坐标
*/
public Vector2f getUV() {
return new Vector2f(uv);
}
/**
* 设置UV坐标
*/
public void setUV(float u, float v) {
this.uv.set(u, v);
}
/**
* 设置UV坐标
*/
public void setUV(Vector2f uv) {
this.uv.set(uv);
}
// ==================== 原有方法 ====================
// Getters and Setters
public Vector2f getPosition() { return new Vector2f(position); }
public void setPosition(float x, float y) { this.position.set(x, y); }
public void setPosition(Vector2f pos) { this.position.set(pos); }
public Vector2f getOriginalPosition() { return new Vector2f(originalPosition); }
public float getInfluenceRadius() { return influenceRadius; }
public void setInfluenceRadius(float radius) { this.influenceRadius = radius; }
public boolean isSelected() { return selected; }
public void setSelected(boolean selected) { this.selected = selected; }
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Map<Integer, Float> getWeightMap() { return new HashMap<>(weightMap); }
public void move(float dx, float dy) {
position.add(dx, dy);
}
public void saveAsOriginal() {
originalPosition.set(position);
}
public void resetToOriginal() {
position.set(originalPosition);
}
public void setId(int id) {
this.id = id;
}
}

View File

@@ -0,0 +1,90 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import java.util.Objects;
/**
* @author tzdwindows 7
*/
public class SecondaryVertex {
Vector2f position;
Vector2f originalPosition;
Vector2f uv;
boolean selected = false;
int id;
static int nextId = 0;
public SecondaryVertex(float x, float y, float u, float v) {
this.position = new Vector2f(x, y);
this.originalPosition = new Vector2f(x, y);
this.uv = new Vector2f(u, v);
this.id = nextId++;
}
public SecondaryVertex(Vector2f position, Vector2f uv) {
this(position.x, position.y, uv.x, uv.y);
}
// Getter和Setter方法
public Vector2f getPosition() { return new Vector2f(position); }
public Vector2f getOriginalPosition() { return new Vector2f(originalPosition); }
public Vector2f getUV() { return new Vector2f(uv); }
public boolean isSelected() { return selected; }
public int getId() { return id; }
public void setPosition(float x, float y) {
this.position.set(x, y);
}
public void setPosition(Vector2f position) {
this.position.set(position);
}
public void setId(int id) {
this.id = id;
}
public void setOriginalPosition(Vector2f originalPosition) {
this.originalPosition.set(originalPosition);
}
public void setUV(float u, float v) {
this.uv.set(u, v);
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public void resetToOriginal() {
this.position.set(originalPosition);
}
public void saveAsOriginal() {
this.originalPosition.set(position);
}
public void move(float dx, float dy) {
this.position.add(dx, dy);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SecondaryVertex that = (SecondaryVertex) o;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return String.format("SecondaryVertex{id=%d, position=(%.2f, %.2f), uv=(%.2f, %.2f)}",
id, position.x, position.y, uv.x, uv.y);
}
}