MySpineEditor/src/components/AtlasRenderCanvasRegion.vue

323 lines
11 KiB
Vue
Raw Normal View History

2024-05-01 17:26:06 +08:00
<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";
type Props = {
atlas: AtlasRenderArgument[],
premultipliedAlpha: boolean,
debug: boolean
}
type Bound = {
width: number,
height: number,
x: number,
y: number
}
type RenderArgument = {
skel: Skeleton,
state: AnimationState,
setupBound: Bound,
edit: boolean
}
const props = defineProps<Props>()
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};
})
})
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: Skeleton | 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 = renderObj.skel;
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.x += mX * 2;
selectSkel.y -= mY * 2;
}
}
}
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.scaleX += deltaScale;
if (selectSkel.scaleX < 0.1){
selectSkel.scaleX = 0.1;
}
if (selectSkel.scaleX > 10){
selectSkel.scaleX = 10;
}
selectSkel.scaleY += deltaScale;
if (selectSkel.scaleY < 0.1){
selectSkel.scaleY = 0.1;
}
if (selectSkel.scaleY > 10){
selectSkel.scaleY = 10;
}
}
}
}
</script>
<template>
<canvas id="mainCanvas" ref="mainCanvas"></canvas>
</template>
<style scoped>
</style>