- 作者:老汪软件技巧
- 发表时间:2024-08-29 04:02
- 浏览量:
源码
/buglas/canv…
学习目标知识点1-多角星简介
多角星就是有多个角的星形,其中有两种角:
内凹角有两种定义方式:
咱们依次说一下这两种绘制方法。
1-1-绘制自定义内圆半径的多角星
已知:
求:点A、B
解:
若求点A、B,只需要求出A、B的弧度即可。
设点A的弧度为α,则:
α=2π/n
所以:
A=(cosα*r1,sinα*r1)
设点B的弧度为θ,则由多角星的定义规则可知:
θ=α*1.5
所以:
B=(cosθ*r2,sinθ*r2)
求出点A、点B后,将其分别旋转(360/n)°,便可算出其余的顶点。
1-2-绘制共线多角星
绘制这种多角星的核心就是将两组外凸角的顶点连线,如直线AB、直线CD,然后以两条直线的交点为内凹角的顶点。
在计算两条直线的交点的时候,需要用到直线的一般式:
Ax+By+C=0
已知两点,可以求出直线的一般式。
比如,已知:两点M(x1,y1),N(x2,y2)
则直线MN的一般式为:
(y2-y1)*x-(x2-x1)*y+(x2-x1)*y1-(y2-y1)*x1=0
从上式中可以提取到A、B、C:
A=△y=y2-y1
B=-△x=-(x2-x1)
C=△x*y1-△y*x1
以此原理,我们可以求出直线AB、CD的一般式:
AB:A1x+B1y+C1=0
CD:A2x+B2y+C2=0
两条直线相交的条件是:
A1B2-A2B1≠0
在此条件下,其交点为:
p.x=(B1C2-C1B2)/(A1B2-A2B1)
p.y=(A2C1-A1C2)/(A1B2-A2B1)
对于交点公式的推导,大家可以参考此文章:《漫谈直线之斜截式和一般式》。
前不久我在初二的数学课本上也看到了相关知识,挺有趣的,分享给大家
北师大版的《初中数学八年级上册》第112页的“读一读”:
我们套用读一读里的这个公式就可以算出交点:
a11、a21对应的是直线一般式里的A;a12、a22对应的是B;b1、b2 对应 -C。
现在思路已通,接下来咱们封装一个直线对象。
2-Line2
Line2 对象是一个数学方法对象,不用于绘图。
import { Vector2 } from './Vector2'
type IntersectCallback=(data:Vector2)=>Vector2|null
class Line2 {
// 起点
start = new Vector2()
// 终点
end = new Vector2()
// 类型
readonly Line2 = true
constructor(start?:Vector2, end?:Vector2) {
start&&(this.start = start)
end&&(this.end = end)
}
set(start: Vector2, end: Vector2) {
this.start.copy(start)
this.end.copy(end)
return this
}
// 直线一般式:Ax+By+C=0,返回A、B、C
ABC() {
const { start, end } = this
const [dx, dy] = [end.x - start.x, end.y - start.y]
const [A, B] = [dy, -dx]
return [A, B, -(A * start.x + B * start.y)]
}
// 与另一条直线的交点
intersect(line: Line2,callback:IntersectCallback=(p:Vector2)=>p):Vector2|null {
const [A1, B1, C1, A2, B2, C2] = [...this.ABC(), ...line.ABC()]
const sub = A1 * B2 - A2 * B1
if (sub) {
const p = new Vector2(
(B1 * C2 - C1 * B2) / sub,
(A2 * C1 - A1 * C2) / sub
)
// 相交,默认返回交点p,可以自定义过滤函数,过滤交点
return callback(p)
} else {
// 平行
return null
}
}
}
export { Line2 }
Line2 对象是通过start 和end 两点确定的,因为两点确定一条直线。
intersect(line,callback) 是利用直线一般式求交的方法,其第二个参数是一个回调函数,可以对计算出的交点做过滤,默认是不过滤的。
接下来,再封装一个线段对象,因为我们要画的多角星是由线段组成的。
3-Segment2
Segment2对象也是一个数学方法对象,不用于绘图,它继承自Line2对象,因为线段也属于直线。
import { Line2 } from './Line2'
import { Vector2 } from './Vector2'
const _v = new Vector2()
/* 此线段是有方向概念的,所以就有了法线、正反面等概念。*/
class Segment2 extends Line2{
/* 线段长度 */
length() {
return this.vector().length()
}
/* 线段长度的平方 */
lengthSq() {
return this.vector().lengthSq()
}
/* 方向:单位向量 */
direction() {
return this.vector().normalize()
}
/* 向量:end-start */
vector() {
const { start, end } = this
return new Vector2(end.x - start.x, end.y - start.y)
}
/* 线段与线段交点 */
intersect(seg: Segment2):Vector2|null {
return super.intersect(seg,(p:Vector2)=>{
if (this.isPointIn(p) && seg.isPointIn(p)) {
return p
}
return null
})
}
/* 法线 */
normal() {
const { start, end } = this
const { x, y } = end.clone().sub(start)
return new Vector2(y, -x).normalize()
}
/* 线段所在的直线上一点是否在线段上 */
isPointIn(p: Vector2) {
const { start, end } = this
const len = _v.subVectors(end, start).lengthSq()
const ls = _v.subVectors(p, start).lengthSq()
const le = _v.subVectors(p, end).lengthSq()
if (ls <= len && le <= len) {
return true
}
return false
}
}
export { Segment2 }
此处的Segment2 对象和数学概念上的线段还有点不同,它有正反面,具备从起点到终点的方向性,拥有法线的概念。
这样可以更方便的做一些图形学方面的测试,把一些复杂的三维算法简化成二维,比如我们后面要说的碰撞反弹。
4-NStarGeometry
NStarGeometry是多角星的几何对象,它继承自PolyGeometry 对象。
import { Line2 } from "../math/Line2";
import { Vector2 } from "../math/Vector2";
import { PolyGeometry } from "./PolyGeometry";
type R2Type = number | 'auto'
const PI = Math.PI
const PI2 = PI * 2
class NStarGeometry extends PolyGeometry{
// 角数
count:number
// 外圆半径
r1:number
// 内圆半径
r2: number
// 是否逆时针绘图
counterclockwise:boolean
// 闭合路径
closePath = true
constructor(r1:number=300,r2:R2Type='auto',count:number=5,counterclockwise:boolean=false){
super()
this.r1=r1
this.count=count
this.counterclockwise=counterclockwise
this.r2=r2 === 'auto'?this.autoR2():r2
this.updatePosition()
}
/* 计算相邻的两个内角顶点共线的内圆半径 */
autoR2() {
const { r1, count } = this
if (count < 5) {
return r1 / 3
}
const space = PI2 / count
const [A, B, C, D] = [0, 2, 1, 3].map((n) => {
const ang = space * n
return new Vector2(Math.cos(ang) * r1, Math.sin(ang) * r1)
})
const intersection = new Line2(A, B).intersect(new Line2(C, D))
if (intersection) {
return intersection.length()
}
return 0
}
// 更新点位
updatePosition() {
const { r1, r2, count,counterclockwise } = this
const dir=counterclockwise?-1:1
const space = PI2 / count*dir
const halfSpace = PI / count*dir
const position: number[] = []
for (let i = 0; i < count; i++) {
const ang1 = space * i
const ang2 = ang1 + halfSpace
position.push(
Math.cos(ang1) * r1,
Math.sin(ang1) * r1,
Math.cos(ang2) * r2,
Math.sin(ang2) * r2
)
}
this.position = position
}
// 克隆
clone() {
return new NStarGeometry().copy(this)
}
// 拷贝
copy(nStarGeometry:NStarGeometry){
const {r1,r2,count,counterclockwise}=nStarGeometry
super.copy(nStarGeometry)
this.r1=r1
this.r2=r2
this.count=count
this.counterclockwise=counterclockwise
return this
}
}
export {NStarGeometry}
当r2值为'auto'时,会通过autoR2()方法自动计算内角半径。当角数小于5时,r2等于r1的1/3;否则,通过外角连线的交点计算r2,其中的算法咱们之前也说过,不再赘述。
5-绘制多角星
建立一个NStar.vue文件用来测试效果。
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Scene } from '../lmm/core/Scene'
import { NStarGeometry } from '../lmm/geometry/NStarGeometry'
import { StandStyle } from '../lmm/style/StandStyle'
import { Graph2D } from '../lmm/objects/Graph2D'
// 获取父级属性
defineProps({
size: { type: Object, default: { width: 0, height: 0 } },
})
// 对应canvas 画布的Ref对象
const canvasRef = ref<HTMLCanvasElement>()
/* 场景 */
const scene = new Scene()
/* 多角星 */
const nStarGeometry=new NStarGeometry(200,150,5,true);
const nStarStyle=new StandStyle({
strokeStyle: '#73e800',
lineWidth: 10,
fillStyle: '#fff693',
})
const nStarObj=new Graph2D(nStarGeometry,nStarStyle)
nStarObj.rotate=-Math.PI / 2
scene.add(nStarObj)
onMounted(() => {
const canvas = canvasRef.value
if (canvas) {
scene.setOption({ canvas })
scene.render()
}
})
script>
<template>
<canvas ref="canvasRef" :width="size.width" :height="size.height">canvas>
template>
<style scoped>style>
效果如下:
这是一颗杨桃的形状。
当我们将r2缓存"auto",r2便会根据两组外凸角的顶点连成的两条直线的交点自动计算。
const nStarGeometry=new NStarGeometry(200,'auto',5,true);
效果如下:
总结
这节课的知识点主要就是三角函数、直线的交点,以及图形的封装。
下一篇我们会说第二道题:绘制小球与多边形的碰撞反弹路径。