feat(render): 添加木偶工具和二级顶点支持- 添加木偶控制点相关字段和方法- 实现木偶控制点的添加、移除和选择功能- 实现基于木偶控制点的网格变形算法
- 添加二级顶点支持及相关操作方法 - 实现二级顶点的渲染和交互功能- 添加变形冲突检测和解决机制 - 实现双线性插值和反距离加权插值算法- 添加控制点影响范围可视化 - 添加二级顶点与网格同步移动功能- 添加变形状态保存和重置功能
This commit is contained in:
@@ -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(){};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中心点
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user