WIP WYSIWYG ~3D SVG editor.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial implementation of RotateTool.

This is definitely going to need to be updated. Rotations don't map well to widgets like this, so we'll need to extract and abstract our basis vectors from getWorldTransforms if this is going to work as expected.

Or maybe we embrace gimbal lock and update drawWidget to show the unbalanced rotation rings.

+126 -1
+126 -1
zoodle.js
··· 31 31 this.tools = { 32 32 orbit: new OrbitTool(this), 33 33 translate: new TranslateTool(this), 34 + rotate: new RotateTool(this), 34 35 }; 35 - this.tool = this.tools.translate; 36 + this.tool = this.tools.rotate; 36 37 37 38 this.presets = { 38 39 solar: new Solar(), ··· 410 411 static get MODE_VIEW() { return 'v'; } 411 412 } 412 413 414 + // TODO: I think this is going to need to run on basis vectors like getWorldTransforms. 415 + class RotateTool extends Tool { 416 + constructor(editor) { 417 + super(editor); 418 + this.targets = null; 419 + this.startRotate = null; 420 + this.mode = RotateTool.MODE_NONE; 421 + this.widget = null; 422 + } 423 + 424 + start(ptr, target, x, y) { 425 + this.widget = target; 426 + this.targets = this.editor.selection; 427 + this.mode = RotateTool.MODE_NONE; 428 + if (!this.targets.length || target.layer !== Zoodle.LAYER_UI || !target.color) { 429 + return; 430 + } 431 + 432 + this.startRotate = this.targets.map( t => t.rotate.copy() ); 433 + 434 + switch (target.color) { 435 + case rose: 436 + this.mode = RotateTool.MODE_X; 437 + break; 438 + case lime: 439 + this.mode = RotateTool.MODE_Y; 440 + break; 441 + case blueberry: 442 + this.mode = RotateTool.MODE_Z; 443 + break; 444 + } 445 + } 446 + move( ptr, target, x, y ) { 447 + if (!this.mode) { return; } 448 + console.log("movin"); 449 + 450 + let displaySize = Math.min( this.editor.scene.width, this.editor.scene.height ); 451 + x /= displaySize / Math.PI * Zdog.TAU; 452 + y /= displaySize / Math.PI * Zdog.TAU; 453 + let direction = this.widget.renderNormal; 454 + let delta = this.editor.getAxisDistance(x, y, Math.atan2(direction.y, direction.x) + TAU/4 ); 455 + this.targets.forEach((t, i) => { 456 + t.rotate[this.mode] = this.startRotate[i][this.mode] + delta; 457 + }); 458 + this.editor.updateHighlights(); 459 + this.editor.updateUI(); 460 + } 461 + // TODO: Let's stash `delta` somewhere so we don't have to recalculate it in end() 462 + end( ptr, target, x, y ) { 463 + if (!this.mode) { return; } 464 + // ensure any final adjustments are applied. 465 + this.move( ptr, target, x, y ); 466 + let displaySize = Math.min( this.editor.scene.width, this.editor.scene.height ); 467 + x /= displaySize * Math.PI * Zdog.TAU; 468 + y /= displaySize * Math.PI * Zdog.TAU; 469 + let direction = this.widget.renderNormal; 470 + let delta = this.editor.getAxisDistance( x, y, Math.atan2(direction.y, direction.x) + TAU/4 ); 471 + let command = new RotateCommand(this.editor, this.targets, new Zdog.Vector({[this.mode]: delta})); 472 + this.editor.did(command); 473 + } 474 + drawWidget(targets) { 475 + const widgetDiameter = 10; 476 + const widgetStroke = 0.75; 477 + 478 + let origin = new Zdog.Shape({ 479 + stroke: .5, 480 + color: lace, 481 + }); 482 + let zRing = new Zdog.Ellipse({ 483 + diameter: widgetDiameter, 484 + stroke: widgetStroke, 485 + color: blueberry, 486 + }); 487 + let yRing = zRing.copyGraph({ 488 + rotate: { x: TAU/4 }, 489 + color: lime, 490 + }); 491 + let xRing = zRing.copyGraph({ 492 + rotate: { y: TAU/4 }, 493 + color: rose, 494 + }); 495 + targets.forEach(t => { 496 + origin.copyGraph({ addTo: t, scale: 1/t.scale.x }); 497 + zRing.copyGraph({ addTo: t, scale: 1/t.scale.x }); 498 + yRing.copyGraph({ addTo: t, scale: 1/t.scale.x }); 499 + xRing.copyGraph({ addTo: t, scale: 1/t.scale.x }); 500 + }); 501 + } 502 + 503 + static get MODE_NONE() { return ''; } 504 + static get MODE_X() { return 'x'; } 505 + static get MODE_Y() { return 'y'; } 506 + static get MODE_Z() { return 'z'; } 507 + } 508 + 413 509 class Command { 414 510 constructor(editor) { 415 511 this.editor = editor; ··· 473 569 474 570 this.target.forEach((t, i) => { 475 571 t.translate.set(this.oldTranslate[i]); 572 + }); 573 + } 574 + } 575 + 576 + class RotateCommand extends Command { 577 + // TODO: Add oldTranslate and oldRotate to the constructor, or maybe a flag to tell it if it's getting new or old transforms. 578 + constructor(editor, target, delta) { 579 + super(editor); 580 + if (!Array.isArray(target)) { 581 + target = [target]; 582 + } 583 + this.target = target; 584 + this.delta = delta; 585 + this.oldRotate = target.map((t) => t.rotate.copy()); 586 + } 587 + 588 + do() { 589 + // TODO: Probably better to just throw in the constructor. 590 + if (!this.target) return console.error("Doing RotateCommand with no target."); 591 + 592 + this.target.forEach((t, i) => { 593 + t.rotate.set(this.oldRotate[i]).add(this.delta); 594 + }); 595 + } 596 + undo() { 597 + if (!this.target) return console.error("Undoing RotateCommand with no target."); 598 + 599 + this.target.forEach((t, i) => { 600 + t.rotate.set(this.oldRotate[i]); 476 601 }); 477 602 } 478 603 }