MySpineEditor/src/components/AtlasRenderCanvasRegion.vue
2024-05-01 22:53:52 +08:00

317 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import AtlasRenderArgument from "../entities/AtlasRenderArgument.ts";
import {computed, onMounted, onUnmounted, ref} from "vue";
import {
AnimationState, AnimationStateData,
AssetManager, AtlasAttachmentLoader,
ManagedWebGLRenderingContext, Matrix4, Physics,
PolygonBatcher,
Shader, ShapeRenderer, Skeleton, SkeletonDebugRenderer, SkeletonJson,
SkeletonRenderer, Vector2
} from "@esotericsoftware/spine-webgl";
import {RenderArgument} from "../entities/RenderArgument.ts";
type Props = {
atlas: AtlasRenderArgument[],
premultipliedAlpha: boolean,
debug: boolean
}
const props = defineProps<Props>();
const emit = defineEmits(['edit']);
const mainCanvas = ref<HTMLCanvasElement>();
let mounted = false;
const webGlCtx = computed<WebGLRenderingContext>(() => mainCanvas.value?.getContext("webgl") as WebGLRenderingContext);
const managedCtx = computed(() => new ManagedWebGLRenderingContext(webGlCtx.value));
const assetManager = computed<AssetManager>(() => new AssetManager(managedCtx.value));
const shader = computed<Shader>(() => Shader.newTwoColoredTextured(managedCtx.value));
const batcher = computed<PolygonBatcher>(() => new PolygonBatcher(managedCtx.value));
const renderer = computed<SkeletonRenderer>(() => new SkeletonRenderer(managedCtx.value));
const rendererArguments = computed<RenderArgument[]>(() => {
return props.atlas.map((argument): RenderArgument => {
const attachmentLoader = new AtlasAttachmentLoader(assetManager.value.require(argument.texture));
const skeletonLoader = new SkeletonJson(attachmentLoader);
skeletonLoader.scale = argument.scale;
const skeletonData = skeletonLoader.readSkeletonData(assetManager.value.require(argument.skel));
const skeleton = new Skeleton(skeletonData);
skeleton.x += argument.x;
skeleton.y += argument.y;
skeleton.setToSetupPose();
skeleton.updateWorldTransform(Physics.update);
let offset = new Vector2();
let size = new Vector2();
skeleton.getBounds(offset, size, []);
const setupBound: Bound = {width: size.x, height: size.y, x: offset.x, y: offset.y};
const animationStateData = new AnimationStateData(skeletonData);
const animationState = new AnimationState(animationStateData);
animationState.setAnimation(0, argument.animation, true);
return {skel: skeleton, setupBound, state: animationState, edit: !!argument.edit, originalRender: argument};
})
})
let timer = Date.now() / 1000;
let mvp = new Matrix4();
// debugger
const debugRenderer = computed<SkeletonDebugRenderer>(() => {
const result = new SkeletonDebugRenderer(managedCtx.value);
result.drawRegionAttachments = true;
result.drawBoundingBoxes = true;
result.drawMeshHull = true;
result.drawMeshTriangles = true;
result.drawPaths = true;
return result;
});
const debugShader = computed<Shader>(() => Shader.newColored(managedCtx.value));
const shape = computed<ShapeRenderer>(() => new ShapeRenderer(managedCtx.value));
const loadingResources = async () => {
props.atlas.forEach((argument) => {
for (let originalImageName in argument.textureImage) {
const blobUrl = argument.textureImage[originalImageName];
const filenameSplit = argument.texture.split("/");
const filename = filenameSplit[filenameSplit.length - 1];
const defaultUrl = argument.texture.replace(filename, originalImageName);
console.log(defaultUrl, filename, originalImageName)
assetManager.value.setRawDataURI(defaultUrl, blobUrl);
}
assetManager.value.loadJson(argument.skel);
assetManager.value.loadTextureAtlas(argument.texture);
});
await assetManager.value.loadAll();
};
const initializationRender = () => {
if (!mainCanvas.value){
console.error("浏览器不支持canvas");
return;
}
mainCanvas.value.getContext("webgl");
}
let viewport: {width: number, height: number} = {width: 0, height: 0};
let selectSkel: {skel: Skeleton, obj: RenderArgument} | null = null;
let isDragging: boolean = false;
const resize = () => {
if (!mainCanvas.value){
return;
}
const w = mainCanvas.value.clientWidth;
const h = mainCanvas.value.clientHeight;
if (mainCanvas.value.width != w || mainCanvas.value.height != h) {
mainCanvas.value.width = w;
mainCanvas.value.height = h;
}
const width = w * 2;
const height = h * 2;
mvp.ortho2d(-(width / 2), -(height / 2), width, height);
viewport.width = w;
viewport.height = h;
managedCtx.value.gl.viewport(0, 0, w, h);
}
const createShaderProgram = (r: number, g: number, b: number) => {
const vsSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
const fsSource = `
void main() {
gl_FragColor = vec4(${r}, ${g}, ${b}, 1.0);
}
`;
const vertexShader = webGlCtx.value.createShader(webGlCtx.value.VERTEX_SHADER) as WebGLShader;
webGlCtx.value.shaderSource(vertexShader, vsSource);
webGlCtx.value.compileShader(vertexShader);
const fragmentShader = webGlCtx.value.createShader(webGlCtx.value.FRAGMENT_SHADER) as WebGLShader;
webGlCtx.value.shaderSource(fragmentShader, fsSource);
webGlCtx.value.compileShader(fragmentShader);
const shaderProgram = webGlCtx.value.createProgram() as WebGLProgram;
webGlCtx.value.attachShader(shaderProgram, vertexShader);
webGlCtx.value.attachShader(shaderProgram, fragmentShader);
webGlCtx.value.linkProgram(shaderProgram);
if (!webGlCtx.value.getProgramParameter(shaderProgram, webGlCtx.value.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + webGlCtx.value.getProgramInfoLog(shaderProgram));
return null;
}
webGlCtx.value.useProgram(shaderProgram);
return shaderProgram;
};
const initBuffers = (x: number, y: number, width: number, height: number, r: number, g: number, b: number) => {
// 创建一个缓冲区对象
const vertexBuffer = webGlCtx.value.createBuffer();
// 绑定缓冲区
webGlCtx.value.bindBuffer(webGlCtx.value.ARRAY_BUFFER, vertexBuffer);
const relativeX = x / viewport.width;
const relativeY = y / viewport.height;
const leftX = relativeX;
const topY = relativeY;
const rightX = relativeX + width / viewport.width;
const bottomY = relativeY + height / viewport.height;
// 定义四个顶点的位置
const vertices = new Float32Array([
leftX, topY, // 第一个顶点x, y
rightX, topY, // 第二个顶点
rightX, bottomY, // 第三个顶点
leftX, bottomY // 第四个顶点
]);
// 将顶点数据传到缓冲区
webGlCtx.value.bufferData(webGlCtx.value.ARRAY_BUFFER, vertices, webGlCtx.value.STATIC_DRAW);
const shaderProgram = createShaderProgram(r, g, b) as WebGLProgram;
// 获取顶点着色器的位置属性
const positionAttribLocation = webGlCtx.value.getAttribLocation(shaderProgram, 'a_position');
webGlCtx.value.enableVertexAttribArray(positionAttribLocation);
webGlCtx.value.vertexAttribPointer(positionAttribLocation, 2, webGlCtx.value.FLOAT, false, 0, 0);
return vertexBuffer;
};
const render = () => {
if (!mainCanvas.value){
console.error("浏览器不支持canvas");
return;
}
if (!mounted){
return;
}
resize();
let now = Date.now() / 1000;
let delta = now - timer;
timer = now;
webGlCtx.value.clearColor(0, 0, 0, 0);
webGlCtx.value.clear(webGlCtx.value.COLOR_BUFFER_BIT);
for (let renderObj of rendererArguments.value) {
let skeleton = renderObj.skel;
let state = renderObj.state;
// let bounds = renderObj.setupBound;
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform(Physics.update);
if (renderObj.edit){
let offset = new Vector2();
let size = new Vector2();
skeleton.getBounds(offset, size, []);
renderObj.setupBound = {width: size.x, height: size.y, x: offset.x, y: offset.y};
}
shader.value.bind();
shader.value.setUniformi(Shader.SAMPLER, 0);
shader.value.setUniform4x4f(Shader.MVP_MATRIX, mvp.values);
batcher.value.begin(shader.value);
renderer.value.premultipliedAlpha = props.premultipliedAlpha;
renderer.value.draw(batcher.value, skeleton);
batcher.value.end();
shader.value.unbind();
if (props.debug) {
debugShader.value.bind();
debugShader.value.setUniform4x4f(Shader.MVP_MATRIX, mvp.values);
debugRenderer.value.premultipliedAlpha = props.premultipliedAlpha;
shape.value.begin(debugShader.value);
debugRenderer.value.draw(shape.value, skeleton);
shape.value.end();
debugShader.value.unbind();
}
}
let hasTouched = false;
selectSkel = null;
for (let renderObj of rendererArguments.value) {
if (!renderObj.edit){
continue;
}
const xMin = Math.min(renderObj.setupBound.x + renderObj.setupBound.width, renderObj.setupBound.x);
const yMin = Math.min(renderObj.setupBound.y + renderObj.setupBound.height, renderObj.setupBound.y);
const xMax = Math.max(renderObj.setupBound.x + renderObj.setupBound.width, renderObj.setupBound.x);
const yMax = Math.max(renderObj.setupBound.y + renderObj.setupBound.height, renderObj.setupBound.y);
if (mousePosition.x > xMin && mousePosition.x < xMax && mousePosition.y > yMin && mousePosition.y < yMax) {
if (!hasTouched){
selectSkel = {skel: renderObj.skel, obj: renderObj};
webGlCtx.value.bindBuffer(webGlCtx.value.ARRAY_BUFFER, initBuffers(renderObj.setupBound.x,renderObj.setupBound.y,renderObj.setupBound.width,renderObj.setupBound.height, 0, 0, 0));
webGlCtx.value.drawArrays(webGlCtx.value.LINE_LOOP, 0, 4);
}
}
}
requestAnimationFrame(render);
}
onMounted(async () => {
if (!mainCanvas.value){
return;
}
mainCanvas.value.addEventListener("mousemove", mouseMove);
mainCanvas.value.addEventListener("mousedown", mouseDown);
mainCanvas.value.addEventListener("mouseup", mouseUp);
window.addEventListener("wheel", wheel, {passive: false});
initializationRender()
await loadingResources();
mounted = true;
requestAnimationFrame(render);
})
onUnmounted(() => {
mounted = false;
});
const mousePosition = new Vector2(0,0);
const mouseMove = (evt: MouseEvent) => {
if (viewport.height === 0 || viewport.width === 0){
return;
}
if (!mainCanvas.value){
return;
}
mousePosition.x = (evt.offsetX - mainCanvas.value.clientWidth / 2) * 2;
mousePosition.y = (-(evt.offsetY - mainCanvas.value.clientHeight / 2)) * 2;
if (isDragging){
const mX = evt.movementX;
const mY = evt.movementY;
if (selectSkel){
selectSkel.skel.x += mX * 2;
selectSkel.skel.y -= mY * 2;
emit("edit", selectSkel);
}
}
}
const mouseDown = () => {
isDragging = true;
}
const mouseUp = () => {
isDragging = false;
}
const wheel = (evt: WheelEvent) => {
if (evt.ctrlKey){
evt.preventDefault();
evt.stopPropagation();
if (selectSkel){
const deltaScale = -evt.deltaY / 1000;
selectSkel.skel.scaleX += deltaScale;
if (selectSkel.skel.scaleX < 0.1){
selectSkel.skel.scaleX = 0.1;
}
if (selectSkel.skel.scaleX > 10){
selectSkel.skel.scaleX = 10;
}
selectSkel.skel.scaleY += deltaScale;
if (selectSkel.skel.scaleY < 0.1){
selectSkel.skel.scaleY = 0.1;
}
if (selectSkel.skel.scaleY > 10){
selectSkel.skel.scaleY = 10;
}
emit("edit", selectSkel);
}
}
}
</script>
<template>
<canvas id="mainCanvas" ref="mainCanvas" v-once></canvas>
</template>
<style scoped>
</style>