bak
This commit is contained in:
1 parent
43933f93d1
commit
03b1147a93
10 files changed
+804
-80
No files matched your search
+80
-22
@@ -140,6 +140,35 @@ const ENGINE_SOURCE = `(function(){
|
||||
draw(t);
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
function drawMaskShape(ctxm,layer,time){
|
||||
var asset=assetMap[layer.assetId];
|
||||
if(!asset) return;
|
||||
if(asset.kind!=='rect' && asset.kind!=='circle' && asset.kind!=='text') return;
|
||||
var p=sample(layer,time);
|
||||
ctxm.save();
|
||||
ctxm.translate(p.x,p.y);
|
||||
ctxm.rotate(p.rotation*Math.PI/180);
|
||||
ctxm.scale(p.scaleX,p.scaleY);
|
||||
ctxm.fillStyle='#000000';
|
||||
if(asset.kind==='rect'){
|
||||
var mrOff=layer.offsetX!=null?layer.offsetX:asset.width/2;
|
||||
var mrOffY=layer.offsetY!=null?layer.offsetY:asset.height/2;
|
||||
ctxm.fillRect(-mrOff,-mrOffY,asset.width,asset.height);
|
||||
} else if(asset.kind==='circle'){
|
||||
var mcOff=layer.offsetX!=null?layer.offsetX:0;
|
||||
var mcOffY=layer.offsetY!=null?layer.offsetY:0;
|
||||
ctxm.beginPath();
|
||||
ctxm.arc(-mcOff,-mcOffY,asset.radius,0,Math.PI*2);
|
||||
ctxm.fill();
|
||||
} else {
|
||||
var mtOff=layer.offsetX!=null?layer.offsetX:0;
|
||||
var mtOffY=layer.offsetY!=null?layer.offsetY:0;
|
||||
ctxm.font=asset.fontSize+'px sans-serif';
|
||||
ctxm.textBaseline='top';
|
||||
ctxm.fillText(asset.text,-mtOff,-mtOffY);
|
||||
}
|
||||
ctxm.restore();
|
||||
}
|
||||
function drawLayer(layer,time){
|
||||
var asset=assetMap[layer.assetId];
|
||||
if(!asset||!layer.visible)return;
|
||||
@@ -233,31 +262,60 @@ const ENGINE_SOURCE = `(function(){
|
||||
var rawOrder=scene.trackOrder||[];
|
||||
var groupById2={}; var groups2=scene.groups||[];
|
||||
for(var gi2=0;gi2<groups2.length;gi2++) groupById2[groups2[gi2].id]=groups2[gi2];
|
||||
var order=[];
|
||||
for(var roi=0;roi<rawOrder.length;roi++){
|
||||
var rid=rawOrder[roi];
|
||||
var grp=groupById2[rid];
|
||||
if(grp){ for(var gc=0;gc<grp.childIds.length;gc++) order.push(grp.childIds[gc]); }
|
||||
else order.push(rid);
|
||||
}
|
||||
if(!order.length){for(var lj=0;lj<scene.layers.length;lj++)order.push(scene.layers[lj].id);}
|
||||
for(var oi=order.length-1;oi>=0;oi--){
|
||||
var id=order[oi];
|
||||
if(!rawOrder.length){ rawOrder=[]; for(var lj=0;lj<scene.layers.length;lj++) rawOrder.push(scene.layers[lj].id); }
|
||||
for(var oi=rawOrder.length-1;oi>=0;oi--){
|
||||
var id=rawOrder[oi];
|
||||
var lyr=layerById[id];
|
||||
if(lyr){ drawLayer(lyr,time); continue; }
|
||||
var tInsts=instByTrack[id]||[];
|
||||
for(var ti=0;ti<tInsts.length;ti++){
|
||||
var inst2=tInsts[ti];
|
||||
var clip2=clipById2[inst2.clipId];
|
||||
if(!clip2)continue;
|
||||
var tS=inst2.trimStart||0;
|
||||
var tE=inst2.trimEnd||0;
|
||||
var visible=Math.max(0,clip2.duration-tS-tE);
|
||||
var offset=time-inst2.startTime;
|
||||
if(offset<0||offset>visible)continue;
|
||||
var localT=tS+offset;
|
||||
for(var cl2=clip2.layers.length-1;cl2>=0;cl2--){
|
||||
drawLayer(clip2.layers[cl2],localT);
|
||||
if(tInsts.length){
|
||||
for(var ti=0;ti<tInsts.length;ti++){
|
||||
var inst2=tInsts[ti];
|
||||
var clip2=clipById2[inst2.clipId];
|
||||
if(!clip2)continue;
|
||||
var tS=inst2.trimStart||0;
|
||||
var tE=inst2.trimEnd||0;
|
||||
var visibleClip=Math.max(0,clip2.duration-tS-tE);
|
||||
var offset=time-inst2.startTime;
|
||||
if(offset<0||offset>visibleClip)continue;
|
||||
var localT=tS+offset;
|
||||
for(var cl2=clip2.layers.length-1;cl2>=0;cl2--){
|
||||
drawLayer(clip2.layers[cl2],localT);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var grp=groupById2[id];
|
||||
if(grp){
|
||||
var gContent=[], gMasks=[];
|
||||
for(var ch=grp.childIds.length-1;ch>=0;ch--){
|
||||
var cl0=layerById[grp.childIds[ch]];
|
||||
if(!cl0) continue;
|
||||
if(cl0.isMask) gMasks.push(cl0); else gContent.push(cl0);
|
||||
}
|
||||
if(!gMasks.length){
|
||||
for(var gi3=0;gi3<gContent.length;gi3++) drawLayer(gContent[gi3],time);
|
||||
} else {
|
||||
var mcanvas=document.createElement('canvas');
|
||||
mcanvas.width=canvas.width; mcanvas.height=canvas.height;
|
||||
var mctx=mcanvas.getContext('2d');
|
||||
var tm=ctx.getTransform();
|
||||
mctx.setTransform(tm.a,tm.b,tm.c,tm.d,tm.e,tm.f);
|
||||
var savedCtx=ctx;
|
||||
ctx=mctx;
|
||||
for(var gi4=0;gi4<gContent.length;gi4++) drawLayer(gContent[gi4],time);
|
||||
for(var gm=0;gm<gMasks.length;gm++){
|
||||
var mL=gMasks[gm];
|
||||
mctx.save();
|
||||
mctx.globalCompositeOperation=(mL.maskMode==='negative'?'destination-out':'destination-in');
|
||||
drawMaskShape(mctx,mL,time);
|
||||
mctx.restore();
|
||||
}
|
||||
ctx=savedCtx;
|
||||
ctx.save();
|
||||
ctx.setTransform(1,0,0,1,0,0);
|
||||
ctx.drawImage(mcanvas,0,0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+101
-14
@@ -1,6 +1,5 @@
|
||||
import type { AnimatableProps, Asset, Layer, Scene } from '@/types/scene'
|
||||
import { sampleLayer } from './interpolate'
|
||||
import { expandedTrackOrder } from './sceneOrder'
|
||||
|
||||
export async function loadSceneImages(scene: Scene): Promise<Record<string, HTMLImageElement>> {
|
||||
const result: Record<string, HTMLImageElement> = {}
|
||||
@@ -145,6 +144,80 @@ function drawLayers(
|
||||
}
|
||||
}
|
||||
|
||||
function drawMaskShape(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
layer: Layer,
|
||||
assetMap: Map<string, Asset>,
|
||||
time: number,
|
||||
) {
|
||||
const asset = assetMap.get(layer.assetId)
|
||||
if (!asset) return
|
||||
if (asset.kind !== 'rect' && asset.kind !== 'circle' && asset.kind !== 'text') return
|
||||
const p = sampleLayer(layer, time)
|
||||
ctx.save()
|
||||
ctx.translate(p.x, p.y)
|
||||
ctx.rotate((p.rotation * Math.PI) / 180)
|
||||
ctx.scale(p.scaleX, p.scaleY)
|
||||
ctx.fillStyle = '#000000'
|
||||
if (asset.kind === 'rect') {
|
||||
const offX = layer.offsetX ?? asset.width / 2
|
||||
const offY = layer.offsetY ?? asset.height / 2
|
||||
ctx.fillRect(-offX, -offY, asset.width, asset.height)
|
||||
} else if (asset.kind === 'circle') {
|
||||
const offX = layer.offsetX ?? 0
|
||||
const offY = layer.offsetY ?? 0
|
||||
ctx.beginPath()
|
||||
ctx.arc(-offX, -offY, asset.radius, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
} else {
|
||||
const offX = layer.offsetX ?? 0
|
||||
const offY = layer.offsetY ?? 0
|
||||
ctx.font = `${asset.fontSize}px sans-serif`
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.fillText(asset.text, -offX, -offY)
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function drawMaskedGroup(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
children: Layer[],
|
||||
assetMap: Map<string, Asset>,
|
||||
images: Record<string, HTMLImageElement>,
|
||||
time: number,
|
||||
) {
|
||||
const masks = children.filter((l) => l.isMask)
|
||||
const content = children.filter((l) => !l.isMask)
|
||||
if (masks.length === 0) {
|
||||
drawLayers(ctx, children, assetMap, images, time)
|
||||
return
|
||||
}
|
||||
|
||||
const w = ctx.canvas.width
|
||||
const h = ctx.canvas.height
|
||||
const off = document.createElement('canvas')
|
||||
off.width = w
|
||||
off.height = h
|
||||
const offCtx = off.getContext('2d')
|
||||
if (!offCtx) {
|
||||
drawLayers(ctx, content, assetMap, images, time)
|
||||
return
|
||||
}
|
||||
const m = ctx.getTransform()
|
||||
offCtx.setTransform(m.a, m.b, m.c, m.d, m.e, m.f)
|
||||
drawLayers(offCtx, content, assetMap, images, time)
|
||||
for (const mask of masks) {
|
||||
offCtx.save()
|
||||
offCtx.globalCompositeOperation = mask.maskMode === 'negative' ? 'destination-out' : 'destination-in'
|
||||
drawMaskShape(offCtx, mask, assetMap, time)
|
||||
offCtx.restore()
|
||||
}
|
||||
ctx.save()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
ctx.drawImage(off, 0, 0)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
export function drawSceneFrame(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
scene: Scene,
|
||||
@@ -174,27 +247,41 @@ export function drawSceneFrame(
|
||||
}
|
||||
const clipById = new Map<string, NonNullable<typeof scene.clips>[number]>()
|
||||
for (const c of scene.clips ?? []) clipById.set(c.id, c)
|
||||
const groupById = new Map<string, NonNullable<typeof scene.groups>[number]>()
|
||||
for (const g of scene.groups ?? []) groupById.set(g.id, g)
|
||||
|
||||
const order = expandedTrackOrder(scene).length
|
||||
? expandedTrackOrder(scene)
|
||||
const rawOrder = scene.trackOrder?.length
|
||||
? scene.trackOrder
|
||||
: scene.layers.map((l) => l.id)
|
||||
for (let i = order.length - 1; i >= 0; i--) {
|
||||
const id = order[i]
|
||||
for (let i = rawOrder.length - 1; i >= 0; i--) {
|
||||
const id = rawOrder[i]
|
||||
const layer = layerById.get(id)
|
||||
if (layer) {
|
||||
drawLayers(ctx, [layer], assetMap, images, time)
|
||||
continue
|
||||
}
|
||||
const instances = instancesByTrack.get(id) ?? []
|
||||
for (const inst of instances) {
|
||||
const clip = clipById.get(inst.clipId)
|
||||
if (!clip) continue
|
||||
const trimStart = inst.trimStart ?? 0
|
||||
const trimEnd = inst.trimEnd ?? 0
|
||||
const visible = Math.max(0, clip.duration - trimStart - trimEnd)
|
||||
const offsetIntoBlock = time - inst.startTime
|
||||
if (offsetIntoBlock < 0 || offsetIntoBlock > visible) continue
|
||||
drawLayers(ctx, clip.layers, assetMap, images, trimStart + offsetIntoBlock)
|
||||
if (instances.length) {
|
||||
for (const inst of instances) {
|
||||
const clip = clipById.get(inst.clipId)
|
||||
if (!clip) continue
|
||||
const trimStart = inst.trimStart ?? 0
|
||||
const trimEnd = inst.trimEnd ?? 0
|
||||
const visible = Math.max(0, clip.duration - trimStart - trimEnd)
|
||||
const offsetIntoBlock = time - inst.startTime
|
||||
if (offsetIntoBlock < 0 || offsetIntoBlock > visible) continue
|
||||
drawLayers(ctx, clip.layers, assetMap, images, trimStart + offsetIntoBlock)
|
||||
}
|
||||
continue
|
||||
}
|
||||
const group = groupById.get(id)
|
||||
if (group) {
|
||||
const childLayers: Layer[] = []
|
||||
for (let k = group.childIds.length - 1; k >= 0; k--) {
|
||||
const cl = layerById.get(group.childIds[k])
|
||||
if (cl) childLayers.push(cl)
|
||||
}
|
||||
drawMaskedGroup(ctx, childLayers, assetMap, images, time)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user